1 /* 2 Permission is hereby granted, free of charge, to any person obtaining a copy of 3 this software and associated documentation files (the "Software"), to deal in 4 the Software without restriction, including without limitation the rights to 5 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 6 of the Software, and to permit persons to whom the Software is furnished to do 7 so, subject to the following conditions: 8 9 The above copyright notice and this permission notice shall be included in all 10 copies or substantial portions of the Software. 11 12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 SOFTWARE. 19 */ 20 21 module pyd.references; 22 23 import std.traits; 24 import std.typetuple; 25 import std.string: format; 26 import std.exception: enforce; 27 28 import pyd.func_wrap; 29 import deimos.python.Python; 30 import pyd.util.multi_index; 31 import pyd.util.typeinfo; 32 33 // s/wrapped_class_object!T/PyObject/ 34 35 /** 36 * A useful check for whether a given class has been wrapped. Mainly used by 37 * the conversion functions (see make_object.d), but possibly useful elsewhere. 38 */ 39 template is_wrapped(T) { 40 alias pyd_references!T.Mapping Mapping; 41 42 static if(!is(Mapping.TypeInfoType!T == T)) { 43 alias is_wrapped!(Mapping.TypeInfoType!T) is_wrapped; 44 }else{ 45 bool is_wrapped = false; 46 } 47 } 48 49 /// _ 50 // wrapped_class_type 51 template PydTypeObject(T) { 52 alias pyd_references!T.Mapping Mapping; 53 54 static if(!is(Mapping.TypeInfoType!T == T)) { 55 alias PydTypeObject!(Mapping.TypeInfoType!T) PydTypeObject; 56 }else{ 57 // The type object, an instance of PyType_Type 58 __gshared PyTypeObject PydTypeObject; 59 } 60 } 61 62 template IsRefParam(alias p) { 63 enum IsRefParam = p == ParameterStorageClass.ref_ || 64 p == ParameterStorageClass.out_; 65 } 66 67 template FnHasRefParams(Fn) { 68 alias anySatisfy!(IsRefParam, ParameterStorageClassTuple!(Fn)) 69 FnHasRefParams; 70 } 71 72 struct DFn_Py_Mapping { 73 const(void)* d; 74 PyObject* py; 75 TypeInfo d_typeinfo; 76 TypeInfo[] params_info; 77 uint functionAttributes; 78 string linkage; 79 Constness constness; 80 81 this(Fn)(Fn d, PyObject* py) if(isFunctionPointer!Fn) { 82 static assert(!FnHasRefParams!Fn, 83 "Pyd cannot handle ref or out parameters at this time"); 84 this.d = DKey(d); 85 this.d_typeinfo = typeid(TypeInfoType!Fn); 86 this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Fn)) 87 .elements; 88 this.py = py; 89 this.functionAttributes = .functionAttributes!Fn; 90 this.linkage = functionLinkage!Fn; 91 this.constness = .constness!Fn; 92 } 93 94 template TypeInfoType(T) if(isFunctionPointer!T) { 95 alias Unqual!( 96 SetFunctionAttributes!(T, functionLinkage!T, 97 FunctionAttribute.none)) TypeInfoType; 98 } 99 100 public static const(void)* DKey(T)(T t) 101 if(isFunctionPointer!T) { 102 return cast(const(void)*) t; 103 } 104 105 public T FromKey(T)() { 106 return cast(T) this.d; 107 } 108 } 109 110 struct DDg_Py_Mapping { 111 const(void)*[2] d; 112 PyObject* py; 113 TypeInfo d_typeinfo; 114 TypeInfo[] params_info; 115 uint functionAttributes; 116 string linkage; 117 Constness constness; 118 119 this(Dg)(Dg d, PyObject* py) if(isDelegate!Dg) { 120 static assert(!FnHasRefParams!Dg, 121 "Pyd cannot handle ref or out parameters at this time"); 122 this.d = DKey(d); 123 this.d_typeinfo = typeid(TypeInfoType!Dg); 124 this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Dg)) 125 .elements; 126 this.py = py; 127 this.functionAttributes = .functionAttributes!Dg; 128 this.linkage = functionLinkage!Dg; 129 this.constness = .constness!Dg; 130 } 131 132 template TypeInfoType(T) { 133 alias Unqual!( 134 SetFunctionAttributes!(T, functionLinkage!T, 135 FunctionAttribute.none)) TypeInfoType; 136 } 137 138 public static const(void)*[2] DKey(T)(T t) 139 if(isDelegate!T) { 140 typeof(return) key; 141 key[0] = cast(const(void)*) t.ptr; 142 key[1] = cast(const(void)*) t.funcptr; 143 return key; 144 } 145 146 public T FromKey(T)() { 147 T x; 148 x.ptr = cast(void*) d[0]; 149 x.funcptr = cast(typeof(x.funcptr)) d[1]; 150 return x; 151 } 152 } 153 154 struct DStruct_Py_Mapping { 155 const(void)* d; 156 PyObject* py; 157 TypeInfo d_typeinfo; 158 Constness constness; 159 160 this(S)(S d, PyObject* py) if(isPointer!S && 161 is(PointerTarget!S == struct)) { 162 this.d = DKey(d); 163 this.d_typeinfo = typeid(TypeInfoType!S); 164 this.py = py; 165 this.constness = .constness!S; 166 } 167 168 template TypeInfoType(T) if(isPointer!T && is(PointerTarget!T == struct)) { 169 alias Unqual!T TypeInfoType; 170 } 171 172 public static const(void)* DKey(T)(T t) 173 if(isPointer!T && is(PointerTarget!T == struct)) { 174 return cast(const(void)*) t; 175 } 176 177 public T FromKey(T)() { 178 return cast(T) this.d; 179 } 180 } 181 182 struct DClass_Py_Mapping { 183 const(void)* d; 184 PyObject* py; 185 TypeInfo d_typeinfo; 186 Constness constness; 187 188 this(S)(S d, PyObject* py) if(is(S == class)) { 189 this.d = cast(const(void)*) d; 190 this.d_typeinfo = typeid(TypeInfoType!S); 191 this.py = py; 192 this.constness = .constness!S; 193 } 194 195 template TypeInfoType(T) if(is(T == class)) { 196 alias Unqual!T TypeInfoType; 197 } 198 199 public static const(void)* DKey(T)(T t) 200 if(is(T == class)) { 201 return cast(const(void)*) t; 202 } 203 204 public T FromKey(T)() { 205 return cast(T) this.d; 206 } 207 } 208 209 /// A bidirectional mapping of a pyobject and the d object associated to it. 210 /// On the python side, these are weak references; we don't want to prevent 211 /// python from reclaiming objects it is finished with. As such, on the D side, 212 /// if you take PyObject*s out of here and store them for an extended time 213 /// elsewhere, be sure to increment the reference count. 214 /// On the D side, we have strong references, but that is incidental to the GC. 215 /// If you stick d objects not allocated with the GC, there will probably be 216 /// leaks. 217 /// We use malloc for the container's structure because we can't use the GC 218 /// inside a destructor and we need to use this container there. 219 template reference_container(Mapping) { 220 static if(is(Mapping == DStruct_Py_Mapping)) { 221 // See #104 for the reason for the hash functions below 222 alias MultiIndexContainer!( 223 Mapping, 224 IndexedBy!( 225 HashedNonUnique!("a.d", "cast(size_t) *cast(const void**) &a"), "d", 226 HashedUnique!("a.py", "cast(size_t) *cast(const void**) &a"), "python" 227 ), 228 MallocAllocator, MutableView) 229 Container; 230 }else{ 231 alias MultiIndexContainer!(Mapping, IndexedBy!( 232 HashedUnique!("a.d"), "d", 233 HashedUnique!("a.py"), "python"), 234 MallocAllocator, MutableView) 235 Container; 236 } 237 Container _reference_container = null; 238 239 @property reference_container() { 240 if(!_reference_container) { 241 _reference_container = Container.create(); 242 Py_AtExit(&clear); 243 } 244 return _reference_container; 245 } 246 247 extern(C) void clear() { 248 if(_reference_container) { 249 _reference_container.d.clear(); 250 } 251 } 252 } 253 254 // A mapping of all GC references that are being held by Python. 255 template pyd_references(T) { 256 static if(isDelegate!T) { 257 alias DDg_Py_Mapping Mapping; 258 }else static if (isFunctionPointer!T) { 259 alias DFn_Py_Mapping Mapping; 260 }else static if(isPointer!T && is(PointerTarget!T == struct)) { 261 alias DStruct_Py_Mapping Mapping; 262 }else static if (is(T == class)) { 263 alias DClass_Py_Mapping Mapping; 264 }else static assert(0, format("type %s cannot sent to pyd, because ??", 265 T.stringof)); 266 alias reference_container!Mapping container; 267 } 268 269 void set_pyd_mapping(T) (PyObject* _self, T t) { 270 alias pyd_references!T.Mapping Mapping; 271 alias pyd_references!T.container container; 272 273 Mapping mapping = Mapping(t, _self); 274 auto py_index = container.python; 275 auto range = py_index.equalRange(_self); 276 if (range.empty) { 277 auto count = py_index.insert(mapping); 278 enforce(count != 0, 279 format("could not add py reference %x for T=%s, t=%s", 280 _self, T.stringof, Mapping.DKey(t))); 281 }else{ 282 auto count = py_index.replace(PSR(range).front, mapping); 283 enforce(count != 0, 284 format("could not update py reference %x for T=%s, t=%s", 285 _self, T.stringof, Mapping.DKey(t))); 286 } 287 } 288 289 void remove_pyd_mapping(T)(PyObject* self) { 290 import std.range; 291 import std.stdio; 292 alias pyd_references!T.Mapping Mapping; 293 alias pyd_references!T.container container; 294 295 auto py_index = container.python; 296 auto range = py_index.equalRange(self); 297 if(!range.empty) { 298 py_index.remove(take(PSR(range),1)); 299 } 300 } 301 302 bool isConversionAddingFunctionAttributes( 303 uint fromTypeFunctionAttributes, 304 uint toTypeFunctionAttributes) { 305 return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0; 306 } 307 308 309 /** 310 * Returns the object contained in a Python wrapped type. 311 */ 312 T get_d_reference(T) (PyObject* _self) { 313 alias pyd_references!T.container container; 314 alias pyd_references!T.Mapping Mapping; 315 import thread = pyd.thread; 316 317 thread.ensureAttached(); 318 319 enforce(is_wrapped!T, 320 format( 321 "Error extracting D object: Type %s is not wrapped.", 322 typeid(T).toString())); 323 enforce(_self !is null, 324 "Error: trying to find D reference for null PyObject*!"); 325 326 auto py_index = container.python; 327 auto range = py_index.equalRange(_self); 328 329 enforce(!range.empty, 330 "Error extracting D object: reference not found!"); 331 // don't cast away type! 332 static if(is(T == class)) { 333 auto tif = cast(TypeInfo_Class) range.front.d_typeinfo; 334 auto t_info = typeid(Mapping.TypeInfoType!T); 335 bool found = false; 336 while(tif) { 337 if(t_info == tif) { 338 found = true; 339 break; 340 } 341 tif = tif.base; 342 } 343 344 enforce(found, 345 format( 346 "Type mismatch extracting D object: found: %s, required: %s", 347 range.front.d_typeinfo, 348 typeid(Mapping.TypeInfoType!T))); 349 350 }else{ 351 enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo, 352 format( 353 "Type mismatch extracting D object: found: %s, required: %s", 354 range.front.d_typeinfo, 355 typeid(Mapping.TypeInfoType!T))); 356 } 357 // don't cast away constness! 358 // mutable => const, etc, okay 359 enforce(constCompatible(range.front.constness, constness!T), 360 format( 361 "constness mismatch required: %s, found: %s", 362 constness_ToString(constness!T), 363 constness_ToString(range.front.constness))); 364 static if(isFunctionPointer!T || isDelegate!T) { 365 // don't cast away linkage! 366 enforce(range.front.linkage == functionLinkage!T, 367 format( 368 "trying to convert a extern(\"%s\") " ~ 369 "%s to extern(\"%s\")", 370 range.front.linkage, (isDelegate!T ? "delegate":"function"), 371 functionLinkage!T)); 372 // losing function attributes is ok, 373 // giving a function new ones, not so much 374 enforce( 375 !isConversionAddingFunctionAttributes( 376 range.front.functionAttributes, functionAttributes!T), 377 format( 378 "trying to convert %s%s to %s", 379 SetFunctionAttributes!(T, 380 functionLinkage!T, 381 FunctionAttribute.none).stringof, 382 attrs_to_string(range.front.functionAttributes), 383 T.stringof)); 384 } 385 386 return range.front.FromKey!T(); 387 } 388 389 /// If the passed D reference has an existing Python object, return a borrowed 390 /// reference to it. Otherwise, return null. 391 PyObject_BorrowedRef* get_python_reference(T) (T t) { 392 alias pyd_references!T.container container; 393 alias pyd_references!T.Mapping Mapping; 394 395 auto d_index = container.d; 396 auto range = d_index.equalRange(Mapping.DKey(t)); 397 static if(is(Mapping == DStruct_Py_Mapping)) { 398 findMatchingType!T(range); 399 } 400 if(range.empty) return null; 401 return borrowed(range.front.py); 402 } 403 404 void findMatchingType(T, R)(ref R range) { 405 while(!range.empty) { 406 if(range.front.d_typeinfo == typeid(T)) { 407 break; 408 } 409 range.popFront(); 410 } 411 } 412 413 /** 414 * Returns a new Python object of a wrapped type. 415 */ 416 // WrapPyObject_FromTypeAndObject 417 // WrapPyObject_FromObject 418 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) { 419 if (!type) { 420 type = &PydTypeObject!T; 421 } 422 if (is_wrapped!(T)) { 423 // If this object is already wrapped, get the existing object. 424 PyObject_BorrowedRef* obj_p = get_python_reference(t); 425 if (obj_p) { 426 return Py_INCREF(obj_p); 427 } 428 // Otherwise, allocate a new object 429 PyObject* obj = type.tp_new(type, null, null); 430 // Set the contained instance 431 set_pyd_mapping(obj, t); 432 return obj; 433 } else { 434 PyErr_SetString(PyExc_RuntimeError, 435 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr); 436 return null; 437 } 438 }