1
#include <torch/csrc/python_headers.h>
3
#include <c10/util/win32-headers.h>
5
#include <structmember.h>
7
#include <ATen/mps/MPSDevice.h>
8
#include <c10/core/CPUAllocator.h>
9
#include <c10/core/RefcountedDeleter.h>
11
#include <torch/csrc/CudaIPCTypes.h>
12
#include <torch/csrc/Device.h>
13
#include <torch/csrc/DynamicTypes.h>
14
#include <torch/csrc/StorageMethods.h>
15
#include <torch/csrc/StorageSharing.h>
16
#include <torch/csrc/THP.h>
17
#include <torch/csrc/autograd/utils/wrap_outputs.h>
18
#include <torch/csrc/copy_utils.h>
19
#include <torch/csrc/utils/pyobject_preservation.h>
20
#include <torch/csrc/utils/python_arg_parser.h>
22
#include <c10/util/intrusive_ptr.h>
23
#include <fmt/format.h>
26
void THPPointer<c10::StorageImpl>::free() {
28
c10::raw::intrusive_ptr::decref(ptr);
32
PyTypeObject* THPStorageClass = nullptr;
34
PyObject* THPStorage_NewWithStorage(
36
c10::Storage _storage,
37
c10::impl::PyInterpreterStatus status,
38
bool allow_preexisting_pyobj) {
40
PyType_IsSubtype(type, &THPStorageType),
41
"Creating a Storage subclass from a class that does not inherit from ",
42
"Storage is not possible. Make sure your class inherits from Storage.");
44
auto maybe_pyobj = _storage.unsafeGetStorageImpl()->pyobj_slot()->check_pyobj(
45
getPyInterpreter(), /*ignore_hermetic_tls=*/false);
46
if (maybe_pyobj.has_value() && maybe_pyobj.value()) {
48
allow_preexisting_pyobj,
49
"Creating a new Storage subclass ",
51
" but the raw Storage object is already associated to a python object ",
53
maybe_pyobj.value()->ob_type->tp_name);
54
PyObject* obj = *maybe_pyobj;
55
PyTypeObject* obj_type = Py_TYPE(obj);
57
obj_type == type || PyType_IsSubtype(obj_type, type),
58
"Creating a new Storage subclass ",
60
" but the raw Storage object is already associated to a python object ",
62
maybe_pyobj.value()->ob_type->tp_name,
63
" which is not a subclass of the "
65
return THPStorage_Wrap(std::move(_storage));
68
PyObject* obj = type->tp_alloc(type, 0);
69
TORCH_CHECK(obj, "Failed to allocate a ", type->tp_name, " object");
71
auto s = (THPStorage*)obj;
73
new (&s->cdata) c10::MaybeOwned<c10::Storage>();
75
s->cdata = c10::MaybeOwned<c10::Storage>::owned(std::move(_storage));
77
if (!c10::impl::HermeticPyObjectTLS::get_state()) {
78
s->is_hermetic = false;
79
const auto& storage = THPStorage_Unpack(s);
80
storage.unsafeGetStorageImpl()->pyobj_slot()->init_pyobj(
81
getPyInterpreter(), obj, status);
83
s->is_hermetic = true;
89
// Wraps the c10::Storage with a storage PyObject
90
PyObject* THPStorage_Wrap(c10::Storage storage) {
91
c10::StorageImpl* storage_impl = storage.unsafeGetStorageImpl();
92
if (c10::impl::HermeticPyObjectTLS::get_state()) {
93
return THPStorage_NewWithStorage(
96
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
98
c10::impl::PyObjectSlot* pyobj_slot = storage_impl->pyobj_slot();
100
// If the StorageImpl has a PyObject that is managed by a different
101
// interpreter than the current one, create a new StorageImpl that points to
102
// the same data and then create the Python storage from that.
103
// NOTE: This is only supposed to happen in MultiPy
104
if (pyobj_slot->has_pyobj_nonhermetic() &&
105
!pyobj_slot->check_interpreter(getPyInterpreter())) {
106
return THPStorage_NewWithStorage(
108
c10::newStorageImplFromRefcountedDataPtr(storage),
109
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
111
c10::optional<PyObject*> maybe_pyobj = pyobj_slot->check_pyobj(
112
getPyInterpreter(), /*ignore_hermetic_tls=*/false);
113
c10::impl::PyInterpreterStatus status =
114
c10::impl::PyInterpreterStatus::TAGGED_BY_US;
115
if (maybe_pyobj.has_value()) {
116
auto obj = *maybe_pyobj;
119
THPStorage_Check(obj),
120
"Expected a storage type, but got ",
121
Py_TYPE(obj)->tp_name);
123
if (pyobj_slot->owns_pyobj()) {
124
pyobj_slot->set_owns_pyobj(false);
125
reinterpret_cast<THPStorage*>(obj)->cdata =
126
c10::MaybeOwned<c10::Storage>::owned(std::move(storage));
133
status = c10::impl::PyInterpreterStatus::TAGGED_BY_US;
135
if (storage.use_count() <= 1) {
136
status = c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED;
138
status = c10::impl::PyInterpreterStatus::MAYBE_UNINITIALIZED;
141
return THPStorage_NewWithStorage(THPStorageClass, std::move(storage), status);
144
static bool THPStorage_isPreservable(THPStorage* self) {
145
if (self->cdata.unsafeIsBorrowed()) {
148
auto const& storage = THPStorage_Unpack(self);
150
if (self->is_hermetic) {
154
if (storage.unsafeGetStorageImpl()->pyobj_slot()->check_pyobj(
155
getPyInterpreter(), /*ignore_hermetic_tls=*/true) !=
156
c10::make_optional((PyObject*)self)) {
159
if (storage.use_count() <= 1) {
165
static bool THPStorage_tryPreserve(THPStorage* self) {
166
if (!THPStorage_isPreservable(self)) {
170
const auto& storage = THPStorage_Unpack(self);
171
c10::StorageImpl* storage_impl = storage.unsafeGetStorageImpl();
173
auto maybe_pyobj = storage_impl->pyobj_slot()->check_pyobj(
175
/*ignore_hermetic_tls=*/true);
176
// NOTE: It is possible to just set the PyObjectSlot here, but the point is
177
// that we should have already set PyObjectSlot when the storage PyObject was
179
TORCH_INTERNAL_ASSERT(
180
maybe_pyobj.has_value(),
181
"Trying to preserve a Python storage whose PyObjectSlot does not have a PyObject");
183
PyObject* pyobj = *maybe_pyobj;
186
THPStorage_Check(pyobj),
187
"Expected a storage type, but got ",
188
Py_TYPE(pyobj)->tp_name);
190
TORCH_INTERNAL_ASSERT(
191
(void*)pyobj == (void*)self,
192
"Python storage and the PyObject in the internal PyObjectSlot are not at the same address");
194
TORCH_INTERNAL_ASSERT(!storage_impl->pyobj_slot()->owns_pyobj());
196
storage_impl->pyobj_slot()->set_owns_pyobj(true);
199
self->cdata = c10::MaybeOwned<c10::Storage>::borrowed(storage);
203
static void THPStorage_subclass_dealloc(PyObject* self) {
204
THPStorage* _self = (THPStorage*)self;
206
if (THPStorage_tryPreserve(_self)) {
210
// Some subclass of StorageBase could be GC-tracked objects even
211
// though the base class is not
212
auto* type = Py_TYPE(self);
213
if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) != 0) {
214
PyObject_GC_UnTrack(self);
217
bool has_finalizer = type->tp_finalize || type->tp_del;
219
if (type->tp_finalize) {
220
PyObject_GC_Track(self);
221
if (PyObject_CallFinalizerFromDealloc(self) < 0) {
222
// The finalizer has resurrected the PyObject and there is a new Python
223
// reference to it, so we can just stop deallocating. Read about
224
// resurrection from `__del__` here:
225
// https://docs.python.org/3/reference/datamodel.html#object.__del__
228
PyObject_GC_UnTrack(self);
231
// base test is unnecessary as THPStorae does not set this
232
if (type->tp_weaklistoffset) {
233
PyObject_ClearWeakRefs(self);
237
PyObject_GC_Track(self);
239
if (self->ob_refcnt > 0) {
240
// Resurrected (see above comment about resurrection from `__del__`)
243
PyObject_GC_UnTrack(self);
247
/* New weakrefs could be created during the finalizer call.
248
If this occurs, clear them out without calling their
249
finalizers since they might rely on part of the object
250
being finalized that has already been destroyed. */
251
if (type->tp_weaklistoffset) {
252
/* Modeled after GET_WEAKREFS_LISTPTR() */
253
PyWeakReference** list =
254
(PyWeakReference**)PyObject_GET_WEAKREFS_LISTPTR(self);
256
_PyWeakref_ClearRef(*list);
262
PyTypeObject* base = type;
263
while (base != &THPStorageType) {
265
clear_slots(base, self);
267
base = base->tp_base;
268
TORCH_INTERNAL_ASSERT(base);
273
if (C10_LIKELY(type->tp_dictoffset)) {
274
PyObject** dictptr = _PyObject_GetDictPtr(self);
275
if (dictptr != nullptr) {
276
PyObject* dict = *dictptr;
277
if (dict != nullptr) {
284
TORCH_INTERNAL_ASSERT(Py_TYPE(self) == type);
286
_self->cdata.~MaybeOwned<c10::Storage>();
287
Py_TYPE(_self)->tp_free(self);
289
TORCH_INTERNAL_ASSERT(type->tp_flags & Py_TPFLAGS_HEAPTYPE);
293
c10::intrusive_ptr<c10::StorageImpl> make_storage_impl(
294
c10::StorageImpl::use_byte_size_t use_byte_size,
295
c10::SymInt size_bytes,
296
c10::Allocator* allocator,
298
c10::optional<int64_t> allocator_opt,
299
c10::optional<at::Device> device_opt) {
300
at::OptionalDeviceGuard device_guard;
301
// This will be non-nullptr only when there is a custom StorageImpl
302
// constructor for the given device
303
c10::StorageImplCreateHelper fptr = nullptr;
304
// For directly passing allocator scenarios, only c10::StorageImpl objects can
305
// be created. If you need to create a storageimpl object of a subclass, you
306
// need to pass in the device information.
307
if (allocator_opt.has_value()) {
308
// NOLINTNEXTLINE(performance-no-int-to-ptr)
309
allocator = reinterpret_cast<c10::Allocator*>(allocator_opt.value());
310
} else if (device_opt.has_value()) {
311
at::Device device = device_opt.value();
312
// We only need to check this here as this is the only case where we can
313
// have a device that is not CPU (and thus for which the StorageImpl
314
// constructor can be overwritten).
315
fptr = c10::GetStorageImplCreate(device.type());
316
if (device.type() == at::kCPU) {
317
allocator = c10::GetDefaultCPUAllocator();
319
} else if (device.type() == at::kCUDA) {
320
at::globalContext().lazyInitCUDA();
321
allocator = c10::cuda::CUDACachingAllocator::get();
324
} else if (device.type() == at::kMPS) {
325
allocator = at::mps::GetMPSAllocator();
327
// NOLINTBEGIN(bugprone-branch-clone)
328
} else if (device.type() == at::DeviceType::XPU) {
329
allocator = c10::GetAllocator(device.type());
330
} else if (device.type() == at::DeviceType::HPU) {
331
allocator = c10::GetAllocator(device.type());
332
} else if (device.type() == at::DeviceType::Meta) {
333
allocator = c10::GetAllocator(device.type());
334
} else if (device.type() == at::DeviceType::PrivateUse1) {
335
at::globalContext().lazyInitPrivateUse1();
336
allocator = c10::GetAllocator(device.type());
338
// NOLINTEND(bugprone-branch-clone)
342
"(): Storage device not recognized: ",
345
device_guard.reset_device(device);
347
allocator = c10::GetDefaultCPUAllocator();
350
if (fptr != nullptr) {
351
return fptr(use_byte_size, std::move(size_bytes), allocator, resizable);
354
// Create a c10::StorageImpl object.
355
return c10::make_intrusive<c10::StorageImpl>(
356
use_byte_size, std::move(size_bytes), allocator, resizable);
359
static PyObject* THPStorage_pynew(
365
type != &THPStorageType,
366
"Cannot directly construct StorageBase; subclass it and then construct that");
367
static torch::PythonArgParser parser({
368
THPStorageStr "(*, int64_t allocator=None, Device device=None)",
370
"(int64_t size, *, int64_t allocator=None, Device device=None)",
372
"(PyObject* sequence, *, int64_t allocator=None, Device device=None)",
374
torch::ParsedArgs<3> parsed_args;
375
auto r = parser.parse(args, kwargs, parsed_args);
377
int allocator_arg_idx = 0;
378
int device_arg_idx = 1;
381
allocator_arg_idx = 1;
385
c10::optional<int64_t> allocator_opt = r.toInt64Optional(allocator_arg_idx);
386
c10::optional<at::Device> device_opt = r.deviceOptional(device_arg_idx);
389
!allocator_opt.has_value() || !device_opt.has_value(),
391
"(): only one or neither of 'allocator' or 'device' can ",
392
"be given, but not both");
394
PyObject* self = nullptr;
395
c10::Allocator* allocator = nullptr;
397
// torch.Storage(*, ...)
399
self = THPStorage_NewWithStorage(
402
c10::StorageImpl::use_byte_size_t(),
408
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
410
// torch.Storage(size, *, ...)
411
} else if (r.idx == 1) {
412
int64_t size = r.toInt64(0);
413
self = THPStorage_NewWithStorage(
416
c10::StorageImpl::use_byte_size_t(),
422
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
424
// torch.Storage(sequence, *, ...)
425
} else if (r.idx == 2) {
426
PyObject* sequence = r.pyobject(0);
427
Py_ssize_t length = PySequence_Length(sequence);
429
PySequence_Check(sequence),
431
"(): Expected a sequence type, but got ",
432
THPUtils_typename(sequence));
436
"(): Could not obtain the length of sequence of type ",
437
THPUtils_typename(sequence));
438
self = THPStorage_NewWithStorage(
441
c10::StorageImpl::use_byte_size_t(),
447
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
450
const auto& storage = THPStorage_Unpack(self);
451
for (Py_ssize_t i = 0; i < length; i++) {
452
item = PySequence_GetItem(sequence, i);
453
uint8_t value = THPByteUtils_unpackReal(item.get());
454
if (allocator == c10::GetDefaultCPUAllocator()) {
455
static_cast<uint8_t*>(storage.mutable_data())[i] = value;
457
// TODO: this might be slow - consider batched updates?
458
storage_set(storage, i, value);
461
} catch (const std::exception& e) {
463
THPStorageStr "(): tried to construct a storage from a sequence (",
464
THPUtils_typename(sequence),
466
"but one of the items was of type ",
467
THPUtils_typename(item.get()),
477
static Py_ssize_t THPStorage_length(THPStorage* self) {
479
THPStorage_assertNotNull(self);
480
return static_cast<Py_ssize_t>(THPStorage_Unpack(self).nbytes());
481
END_HANDLE_TH_ERRORS_RET(-1)
484
static PyObject* THPStorage_get(THPStorage* self, PyObject* index) {
486
THPStorage_assertNotNull(self);
487
const auto& storage = THPStorage_Unpack(self);
488
int64_t len = static_cast<int64_t>(storage.nbytes());
490
if (THPUtils_checkLong(index)) {
491
int64_t nindex = THPUtils_unpackLong(index);
494
if (nindex < 0 || nindex >= len) {
498
"index {} out of range for storage of size {}", nindex, len));
501
uint8_t value = storage_get(storage, nindex);
502
return THPByteUtils_newReal(value);
504
} else if (PySlice_Check(index)) {
505
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
506
Py_ssize_t start, stop, slicelength, step;
507
if (PySlice_Unpack(index, &start, &stop, &step) < 0) {
510
slicelength = PySlice_AdjustIndices(len, &start, &stop, step);
513
"Trying to slice with a step of ",
515
", but only a step of "
520
const auto& storage = THPStorage_Unpack(self);
521
auto data = static_cast<uint8_t*>(storage.mutable_data());
523
at::StorageImpl* old_storage_impl = storage.unsafeGetStorageImpl();
524
c10::raw::intrusive_ptr::incref(old_storage_impl);
525
auto new_storage_impl = c10::make_intrusive<at::StorageImpl>(
526
c10::StorageImpl::use_byte_size_t(),
528
slicelength * sizeof(quantized_t),
533
static_cast<void*>(data + start),
536
c10::raw::intrusive_ptr::decref(static_cast<at::StorageImpl*>(s));
538
old_storage_impl->device()),
539
old_storage_impl->allocator(),
540
/* resizable */ false);
542
PyObject* _ret = THPStorage_NewWithStorage(
544
std::move(new_storage_impl),
545
c10::impl::PyInterpreterStatus::DEFINITELY_UNINITIALIZED);
551
"can't index a " THPStorageStr " with %s",
552
THPUtils_typename(index));
557
static int THPStorage_set(THPStorage* self, PyObject* index, PyObject* value) {
559
THPStorage_assertNotNull(self);
560
if (!THPByteUtils_checkReal(value)) {
562
"can only set storage content with a int types, but got ",
563
THPUtils_typename(value),
568
uint8_t rvalue = THPByteUtils_unpackReal(value);
569
const auto& storage = THPStorage_Unpack(self);
570
if (THPUtils_checkLong(index)) {
571
int64_t nindex = THPUtils_unpackLong(index);
572
storage_set(storage, nindex, rvalue);
574
} else if (PySlice_Check(index)) {
575
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
576
Py_ssize_t start, stop, step;
577
Py_ssize_t len = static_cast<Py_ssize_t>(storage.nbytes());
578
if (PySlice_Unpack(index, &start, &stop, &step) < 0) {
581
PySlice_AdjustIndices(len, &start, &stop, step);
584
"Trying to slice with a step of ",
586
", but only a step of "
590
// TODO: check the bounds only once
592
for (; start < stop; start++)
593
storage_set(storage, start, rvalue);
597
"can't index a " THPStorageStr " with ", THPUtils_typename(index));
599
END_HANDLE_TH_ERRORS_RET(-1)
602
static PyMappingMethods THPStorage_mappingmethods = {
603
(lenfunc)THPStorage_length,
604
(binaryfunc)THPStorage_get,
605
(objobjargproc)THPStorage_set};
607
struct THPStorageMeta {
608
PyHeapTypeObject base;
611
int THPStorageMetaType_init(PyObject* cls, PyObject* args, PyObject* kwargs);
613
PyTypeObject THPStorageMetaType = {
614
PyVarObject_HEAD_INIT(
615
DEFERRED_ADDRESS(&PyType_Type),
616
0) "torch._C._StorageMeta", /* tp_name */
617
sizeof(THPStorageMeta), /* tp_basicsize */
619
nullptr, /* tp_dealloc */
620
0, /* tp_vectorcall_offset */
621
nullptr, /* tp_getattr */
622
nullptr, /* tp_setattr */
623
nullptr, /* tp_reserved */
624
nullptr, /* tp_repr */
625
nullptr, /* tp_as_number */
626
nullptr, /* tp_as_sequence */
627
nullptr, /* tp_as_mapping */
628
nullptr, /* tp_hash */
629
nullptr, /* tp_call */
630
nullptr, /* tp_str */
631
nullptr, /* tp_getattro */
632
nullptr, /* tp_setattro */
633
nullptr, /* tp_as_buffer */
634
// NOLINTNEXTLINE(misc-redundant-expression)
635
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
636
nullptr, /* tp_doc */
637
nullptr, /* tp_traverse */
638
nullptr, /* tp_clear */
639
nullptr, /* tp_richcompare */
640
0, /* tp_weaklistoffset */
641
nullptr, /* tp_iter */
642
nullptr, /* tp_iternext */
643
nullptr, /* tp_methods */
644
nullptr, /* tp_members */
645
nullptr, /* tp_getset */
646
DEFERRED_ADDRESS(&PyType_Type), /* tp_base */
647
nullptr, /* tp_dict */
648
nullptr, /* tp_descr_get */
649
nullptr, /* tp_descr_set */
650
0, /* tp_dictoffset */
651
THPStorageMetaType_init, /* tp_init */
652
nullptr, /* tp_alloc */
653
nullptr, /* tp_new */
656
// TODO: implement equality
657
PyTypeObject THPStorageType = {
658
PyVarObject_HEAD_INIT(
660
0) "torch._C.StorageBase", /* tp_name */
661
sizeof(THPStorage), /* tp_basicsize */
663
nullptr, /* tp_dealloc */
664
0, /* tp_vectorcall_offset */
665
nullptr, /* tp_getattr */
666
nullptr, /* tp_setattr */
667
nullptr, /* tp_reserved */
668
nullptr, /* tp_repr */
669
nullptr, /* tp_as_number */
670
nullptr, /* tp_as_sequence */
671
&THPStorage_mappingmethods, /* tp_as_mapping */
672
nullptr, /* tp_hash */
673
nullptr, /* tp_call */
674
nullptr, /* tp_str */
675
nullptr, /* tp_getattro */
676
nullptr, /* tp_setattro */
677
nullptr, /* tp_as_buffer */
678
// NOLINTNEXTLINE(misc-redundant-expression)
679
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
680
nullptr, /* tp_doc */
681
nullptr, /* tp_traverse */
682
nullptr, /* tp_clear */
683
nullptr, /* tp_richcompare */
684
0, /* tp_weaklistoffset */
685
nullptr, /* tp_iter */
686
nullptr, /* tp_iternext */
688
/* will be assigned in init */ /* tp_methods */
690
/* will be assigned in init */ /* tp_members */
691
nullptr, /* tp_getset */
692
nullptr, /* tp_base */
693
nullptr, /* tp_dict */
694
nullptr, /* tp_descr_get */
695
nullptr, /* tp_descr_set */
696
0, /* tp_dictoffset */
697
nullptr, /* tp_init */
698
nullptr, /* tp_alloc */
699
THPStorage_pynew, /* tp_new */
702
int THPStorageMetaType_init(PyObject* cls, PyObject* args, PyObject* kwargs) {
703
if (PyType_Type.tp_init(cls, args, kwargs) < 0) {
706
((PyTypeObject*)cls)->tp_dealloc = (destructor)THPStorage_subclass_dealloc;
710
static PyObject* THPStorage_device(THPStorage* self, void* unused) {
712
THPStorage_assertNotNull(self);
713
return THPDevice_New(THPStorage_Unpack(self).device());
717
PyObject* THPStorage_get_cdata(THPStorage* self, void* unused) {
719
return PyLong_FromVoidPtr(THPStorage_Unpack(self).unsafeGetStorageImpl());
723
typedef PyObject* (*getter)(PyObject*, void*);
725
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-non-const-global-variables)
726
static struct PyGetSetDef THPStorage_properties[] = {
727
{"device", (getter)THPStorage_device, nullptr, nullptr, nullptr},
728
{"_cdata", (getter)THPStorage_get_cdata, nullptr, nullptr, nullptr},
731
bool THPStorage_init(PyObject* module) {
732
static std::vector<PyMethodDef> methods;
733
THPUtils_addPyMethodDefs(methods, THPStorage_getMethods());
734
THPUtils_addPyMethodDefs(methods, THPStorage_getSharingMethods());
736
THPStorageMetaType.tp_base = &PyType_Type;
737
if (PyType_Ready(&THPStorageMetaType) < 0)
739
Py_INCREF(&THPStorageMetaType);
740
PyModule_AddObject(module, "_StorageMeta", (PyObject*)&THPStorageMetaType);
742
THPStorageType.tp_methods = methods.data();
743
THPStorageType.tp_getset = THPStorage_properties;
744
if (PyType_Ready(&THPStorageType) < 0)
746
Py_INCREF(&THPStorageType);
747
PyModule_AddObject(module, "StorageBase", (PyObject*)&THPStorageType);
751
void THPStorage_postInit(PyObject* module) {
753
(PyTypeObject*)PyObject_GetAttrString(module, "UntypedStorage");
754
if (!THPStorageClass)
755
throw python_error();
758
void THPStorage_assertNotNull(THPStorage* storage) {
760
THPStorage_Unpack(storage).unsafeGetStorageImpl(), "Got a null Storage");
763
void THPStorage_assertNotNull(PyObject* obj) {
764
THPStorage_assertNotNull((THPStorage*)obj);