File: | build/../torch/csrc/Device.cpp |
Warning: | line 148, column 27 PyObject ownership leak with reference count of 1 |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | #include <torch/csrc/Device.h> | |||
2 | ||||
3 | #include <torch/csrc/Exceptions.h> | |||
4 | #include <torch/csrc/utils/object_ptr.h> | |||
5 | #include <torch/csrc/utils/python_arg_parser.h> | |||
6 | #include <torch/csrc/utils/python_strings.h> | |||
7 | #include <torch/csrc/utils/python_numbers.h> | |||
8 | #include <torch/csrc/utils/pybind.h> | |||
9 | ||||
10 | #include <ATen/Device.h> | |||
11 | #include <c10/util/Exception.h> | |||
12 | ||||
13 | #include <cstring> | |||
14 | #include <limits> | |||
15 | #include <structmember.h> | |||
16 | #include <sstream> | |||
17 | ||||
18 | PyObject *THPDevice_New(const at::Device& device) | |||
19 | { | |||
20 | auto type = (PyTypeObject*)&THPDeviceType; | |||
21 | auto self = THPObjectPtr{type->tp_alloc(type, 0)}; | |||
22 | if (!self) throw python_error(); | |||
23 | auto self_ = reinterpret_cast<THPDevice*>(self.get()); | |||
24 | self_->device = device; | |||
25 | return self.release(); | |||
26 | } | |||
27 | ||||
28 | PyObject *THPDevice_repr(THPDevice *self) | |||
29 | { | |||
30 | std::ostringstream oss; | |||
31 | oss << "device(type=\'" << self->device.type() << "\'"; | |||
32 | if (self->device.has_index()) { | |||
33 | // `self->device.index()` returns uint8_t which is treated as ascii while printing, | |||
34 | // hence casting it to uint16_t. | |||
35 | // https://stackoverflow.com/questions/19562103/uint8-t-cant-be-printed-with-cout | |||
36 | oss << ", index=" << static_cast<uint16_t>(self->device.index()); | |||
37 | } | |||
38 | oss << ")"; | |||
39 | return THPUtils_packString(oss.str().c_str()); | |||
40 | } | |||
41 | ||||
42 | PyObject *THPDevice_str(THPDevice *self) | |||
43 | { | |||
44 | std::ostringstream oss; | |||
45 | oss << self->device; | |||
46 | return THPUtils_packString(oss.str().c_str()); | |||
47 | } | |||
48 | ||||
49 | PyObject *THPDevice_pynew(PyTypeObject *type, PyObject *args, PyObject *kwargs) | |||
50 | { | |||
51 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
52 | static torch::PythonArgParser parser({ | |||
53 | "Device(Device device)", | |||
54 | "Device(c10::string_view type, int64_t? index=-1)" | |||
55 | }); | |||
56 | torch::ParsedArgs<2> parsed_args; | |||
57 | auto r = parser.parse(args, kwargs, parsed_args); | |||
58 | if (r.idx == 0) { | |||
59 | auto device = r.device(0); | |||
60 | return THPDevice_New(device); | |||
61 | } else if (r.idx == 1) { | |||
62 | auto as_device = r.device(0); // this works, because device can take strings | |||
63 | auto device_type = r.string(0); | |||
64 | if (as_device.has_index()) { | |||
65 | throw std::runtime_error("type (string) must not include an index because index " | |||
66 | "was passed explicitly: " + device_type); | |||
67 | } | |||
68 | int32_t device_index = -1; | |||
69 | if (!r.isNone(1)) { | |||
70 | device_index = r.toInt64(1); | |||
71 | // -1 is allowed in ATen/C++, to mean the default device, but not in | |||
72 | // Python. | |||
73 | TORCH_CHECK(device_index >= 0, "Device index must not be negative")if ((__builtin_expect(static_cast<bool>(!(device_index >= 0)), 0))) { ::c10::detail::torchCheckFail( __func__, "../torch/csrc/Device.cpp" , static_cast<uint32_t>(73), (::c10::detail::torchCheckMsgImpl ( "Expected " "device_index >= 0" " to be true, but got false. " "(Could this error message be improved? If so, " "please report an enhancement request to PyTorch.)" , "Device index must not be negative"))); }; | |||
74 | } | |||
75 | at::Device device(as_device.type(), device_index); | |||
76 | return THPDevice_New(device); | |||
77 | } | |||
78 | Py_RETURN_NONEreturn _Py_INCREF(((PyObject*)((&_Py_NoneStruct)))), (& _Py_NoneStruct); | |||
79 | END_HANDLE_TH_ERRORS} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return nullptr ; } catch (const c10::IndexError& e) { auto msg = torch:: get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace (); PyErr_SetString(PyExc_IndexError, torch::processErrorMsg( msg)); return nullptr; } catch (const c10::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return nullptr; } catch (torch ::PyTorchError & e) { auto msg = torch::processErrorMsg(e .what()); PyErr_SetString(e.python_type(), msg); return nullptr ; } catch (const std::exception& e) { auto msg = torch::processErrorMsg (e.what()); PyErr_SetString(PyExc_RuntimeError, msg); return nullptr ; } | |||
80 | } | |||
81 | ||||
82 | PyObject *THPDevice_type(THPDevice *self, PyObject *noargs) | |||
83 | { | |||
84 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
85 | std::ostringstream oss; | |||
86 | oss << self->device.type(); | |||
87 | return THPUtils_packString(oss.str().c_str()); | |||
88 | Py_RETURN_NONEreturn _Py_INCREF(((PyObject*)((&_Py_NoneStruct)))), (& _Py_NoneStruct); | |||
89 | END_HANDLE_TH_ERRORS} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return nullptr ; } catch (const c10::IndexError& e) { auto msg = torch:: get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace (); PyErr_SetString(PyExc_IndexError, torch::processErrorMsg( msg)); return nullptr; } catch (const c10::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return nullptr; } catch (torch ::PyTorchError & e) { auto msg = torch::processErrorMsg(e .what()); PyErr_SetString(e.python_type(), msg); return nullptr ; } catch (const std::exception& e) { auto msg = torch::processErrorMsg (e.what()); PyErr_SetString(PyExc_RuntimeError, msg); return nullptr ; } | |||
90 | } | |||
91 | ||||
92 | PyObject *THPDevice_index(THPDevice *self, PyObject *noargs) | |||
93 | { | |||
94 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
95 | if (self->device.has_index()) { | |||
96 | return THPUtils_packInt64(self->device.index()); | |||
97 | } else { | |||
98 | Py_RETURN_NONEreturn _Py_INCREF(((PyObject*)((&_Py_NoneStruct)))), (& _Py_NoneStruct); | |||
99 | } | |||
100 | END_HANDLE_TH_ERRORS} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return nullptr ; } catch (const c10::IndexError& e) { auto msg = torch:: get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace (); PyErr_SetString(PyExc_IndexError, torch::processErrorMsg( msg)); return nullptr; } catch (const c10::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return nullptr; } catch (torch ::PyTorchError & e) { auto msg = torch::processErrorMsg(e .what()); PyErr_SetString(e.python_type(), msg); return nullptr ; } catch (const std::exception& e) { auto msg = torch::processErrorMsg (e.what()); PyErr_SetString(PyExc_RuntimeError, msg); return nullptr ; } | |||
101 | } | |||
102 | ||||
103 | static Py_ssize_t THPDevice_hash(THPDevice *self) | |||
104 | { | |||
105 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
106 | return static_cast<Py_ssize_t>(std::hash<at::Device>{}(self->device) % std::numeric_limits<Py_ssize_t>::max()); | |||
107 | END_HANDLE_TH_ERRORS_RET(-1)} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return -1; } catch (const c10::IndexError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_IndexError , torch::processErrorMsg(msg)); return -1; } catch (const c10 ::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return -1; } catch (const c10 ::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return -1; } catch (const c10 ::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return -1; } catch (const c10 ::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return -1; } catch (torch::PyTorchError & e) { auto msg = torch::processErrorMsg(e.what()); PyErr_SetString (e.python_type(), msg); return -1; } catch (const std::exception & e) { auto msg = torch::processErrorMsg(e.what()); PyErr_SetString (PyExc_RuntimeError, msg); return -1; } | |||
108 | } | |||
109 | ||||
110 | PyObject *THPDevice_rc(PyObject *a, PyObject *b, int op) { | |||
111 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
112 | if (!THPDevice_Check(a) || !THPDevice_Check(b)) { | |||
113 | // Py_RETURN_NOTIMPLEMENTED not in python 2. | |||
114 | Py_INCREF(Py_NotImplemented)_Py_INCREF(((PyObject*)((&_Py_NotImplementedStruct)))); | |||
115 | return Py_NotImplemented(&_Py_NotImplementedStruct); | |||
116 | } | |||
117 | THPDevice *da = reinterpret_cast<THPDevice*>(a); | |||
118 | THPDevice *db = reinterpret_cast<THPDevice*>(b); | |||
119 | ||||
120 | switch(op) { | |||
121 | case Py_EQ2: | |||
122 | if (da->device == db->device) { | |||
123 | Py_RETURN_TRUEreturn _Py_INCREF(((PyObject*)(((PyObject *) &_Py_TrueStruct )))), ((PyObject *) &_Py_TrueStruct); | |||
124 | } else { | |||
125 | Py_RETURN_FALSEreturn _Py_INCREF(((PyObject*)(((PyObject *) &_Py_FalseStruct )))), ((PyObject *) &_Py_FalseStruct); | |||
126 | } | |||
127 | case Py_NE3: | |||
128 | if (da->device == db->device) { | |||
129 | Py_RETURN_FALSEreturn _Py_INCREF(((PyObject*)(((PyObject *) &_Py_FalseStruct )))), ((PyObject *) &_Py_FalseStruct); | |||
130 | } else { | |||
131 | Py_RETURN_TRUEreturn _Py_INCREF(((PyObject*)(((PyObject *) &_Py_TrueStruct )))), ((PyObject *) &_Py_TrueStruct); | |||
132 | } | |||
133 | case Py_LT0: | |||
134 | case Py_LE1: | |||
135 | case Py_GT4: | |||
136 | case Py_GE5: | |||
137 | throw torch::TypeError("comparison not implemented"); | |||
138 | default: | |||
139 | throw torch::TypeError("unexpected comparison op"); | |||
140 | } | |||
141 | END_HANDLE_TH_ERRORS} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return nullptr ; } catch (const c10::IndexError& e) { auto msg = torch:: get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace (); PyErr_SetString(PyExc_IndexError, torch::processErrorMsg( msg)); return nullptr; } catch (const c10::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return nullptr; } catch (torch ::PyTorchError & e) { auto msg = torch::processErrorMsg(e .what()); PyErr_SetString(e.python_type(), msg); return nullptr ; } catch (const std::exception& e) { auto msg = torch::processErrorMsg (e.what()); PyErr_SetString(PyExc_RuntimeError, msg); return nullptr ; } | |||
142 | } | |||
143 | ||||
144 | PyObject *THPDevice_reduce(PyObject *_self, PyObject *noargs) | |||
145 | { | |||
146 | HANDLE_TH_ERRORStry { torch::PyWarningHandler __enforce_warning_buffer; try { | |||
147 | auto self = (THPDevice*)_self; | |||
148 | auto ret = THPObjectPtr{PyTuple_New(2)}; | |||
| ||||
| ||||
149 | if (!ret) throw python_error(); | |||
150 | ||||
151 | py::object torch_module = py::module::import("torch"); | |||
152 | py::object torch_device = torch_module.attr("device"); | |||
153 | PyTuple_SET_ITEM(ret.get(), 0, torch_device.release().ptr())PyTuple_SetItem(ret.get(), 0, torch_device.release().ptr()); | |||
154 | ||||
155 | THPObjectPtr args; | |||
156 | std::ostringstream oss; | |||
157 | oss << self->device.type(); | |||
158 | if (self->device.has_index()) { | |||
159 | args = THPObjectPtr{Py_BuildValue("(si)", oss.str().c_str(), self->device.index())}; | |||
160 | } else { | |||
161 | args = THPObjectPtr{Py_BuildValue("(s)", oss.str().c_str())}; | |||
162 | } | |||
163 | if (!args) throw python_error(); | |||
164 | PyTuple_SET_ITEM(ret.get(), 1, args.release())PyTuple_SetItem(ret.get(), 1, args.release()); | |||
165 | ||||
166 | return ret.release(); | |||
167 | END_HANDLE_TH_ERRORS} catch(...) { __enforce_warning_buffer.set_in_exception(); throw ; } } catch (python_error & e) { e.restore(); return nullptr ; } catch (const c10::IndexError& e) { auto msg = torch:: get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace (); PyErr_SetString(PyExc_IndexError, torch::processErrorMsg( msg)); return nullptr; } catch (const c10::ValueError& e) { auto msg = torch::get_cpp_stacktraces_enabled() ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_ValueError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::TypeError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_TypeError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::NotImplementedError& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_NotImplementedError , torch::processErrorMsg(msg)); return nullptr; } catch (const c10::Error& e) { auto msg = torch::get_cpp_stacktraces_enabled () ? e.what() : e.what_without_backtrace(); PyErr_SetString(PyExc_RuntimeError , torch::processErrorMsg(msg)); return nullptr; } catch (torch ::PyTorchError & e) { auto msg = torch::processErrorMsg(e .what()); PyErr_SetString(e.python_type(), msg); return nullptr ; } catch (const std::exception& e) { auto msg = torch::processErrorMsg (e.what()); PyErr_SetString(PyExc_RuntimeError, msg); return nullptr ; } | |||
168 | } | |||
169 | ||||
170 | typedef PyObject *(*getter)(PyObject *, void *); | |||
171 | ||||
172 | // NB: If you edit these properties/methods, update torch/_C/__init__.pyi.in | |||
173 | ||||
174 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,cppcoreguidelines-avoid-non-const-global-variables,modernize-avoid-c-arrays) | |||
175 | static struct PyGetSetDef THPDevice_properties[] = { | |||
176 | {"type", (getter)THPDevice_type, nullptr, nullptr, nullptr}, | |||
177 | {"index", (getter)THPDevice_index, nullptr, nullptr, nullptr}, | |||
178 | {nullptr} | |||
179 | }; | |||
180 | ||||
181 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,cppcoreguidelines-avoid-non-const-global-variables,modernize-avoid-c-arrays) | |||
182 | static PyMethodDef THPDevice_methods[] = { | |||
183 | {"__reduce__", THPDevice_reduce, METH_NOARGS0x0004, nullptr}, | |||
184 | {nullptr} /* Sentinel */ | |||
185 | }; | |||
186 | ||||
187 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |||
188 | PyTypeObject THPDeviceType = { | |||
189 | PyVarObject_HEAD_INIT(nullptr, 0){ { 1, nullptr }, 0 }, | |||
190 | "torch.device", /* tp_name */ | |||
191 | sizeof(THPDevice), /* tp_basicsize */ | |||
192 | 0, /* tp_itemsize */ | |||
193 | nullptr, /* tp_dealloc */ | |||
194 | // NOLINTNEXTLINE(modernize-use-nullptr) | |||
195 | 0, /* tp_vectorcall_offset */ | |||
196 | nullptr, /* tp_getattr */ | |||
197 | nullptr, /* tp_setattr */ | |||
198 | nullptr, /* tp_reserved */ | |||
199 | (reprfunc)THPDevice_repr, /* tp_repr */ | |||
200 | nullptr, /* tp_as_number */ | |||
201 | nullptr, /* tp_as_sequence */ | |||
202 | nullptr, /* tp_as_mapping */ | |||
203 | (hashfunc)THPDevice_hash, /* tp_hash */ | |||
204 | nullptr, /* tp_call */ | |||
205 | (reprfunc)THPDevice_str, /* tp_str */ | |||
206 | nullptr, /* tp_getattro */ | |||
207 | nullptr, /* tp_setattro */ | |||
208 | nullptr, /* tp_as_buffer */ | |||
209 | Py_TPFLAGS_DEFAULT( 0 | (1UL << 18) | 0), /* tp_flags */ | |||
210 | nullptr, /* tp_doc */ | |||
211 | nullptr, /* tp_traverse */ | |||
212 | nullptr, /* tp_clear */ | |||
213 | (richcmpfunc)THPDevice_rc, /* tp_richcompare */ | |||
214 | 0, /* tp_weaklistoffset */ | |||
215 | nullptr, /* tp_iter */ | |||
216 | nullptr, /* tp_iternext */ | |||
217 | THPDevice_methods, /* tp_methods */ | |||
218 | nullptr, /* tp_members */ | |||
219 | THPDevice_properties, /* tp_getset */ | |||
220 | nullptr, /* tp_base */ | |||
221 | nullptr, /* tp_dict */ | |||
222 | nullptr, /* tp_descr_get */ | |||
223 | nullptr, /* tp_descr_set */ | |||
224 | 0, /* tp_dictoffset */ | |||
225 | nullptr, /* tp_init */ | |||
226 | nullptr, /* tp_alloc */ | |||
227 | THPDevice_pynew, /* tp_new */ | |||
228 | }; | |||
229 | ||||
230 | void THPDevice_init(PyObject *module) | |||
231 | { | |||
232 | if (PyType_Ready(&THPDeviceType) < 0) { | |||
233 | throw python_error(); | |||
234 | } | |||
235 | Py_INCREF(&THPDeviceType)_Py_INCREF(((PyObject*)(&THPDeviceType))); | |||
236 | if (PyModule_AddObject(module, "device", (PyObject *)&THPDeviceType) != 0) { | |||
237 | throw python_error(); | |||
238 | } | |||
239 | } |
1 | #ifndef PyTuple_New |
2 | struct _object; |
3 | typedef struct _object PyObject; |
4 | PyObject* clang_analyzer_PyObject_New_Reference(); |
5 | PyObject* PyTuple_New(Py_ssize_t len) { |
6 | return clang_analyzer_PyObject_New_Reference(); |
7 | } |
8 | #else |
9 | #warning "API PyTuple_New is defined as a macro." |
10 | #endif |