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 util.multi_index; 31 import 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 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 = new Container(); 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 import std.stdio; 271 alias pyd_references!T.Mapping Mapping; 272 alias pyd_references!T.container container; 273 274 Mapping mapping = Mapping(t, _self); 275 auto py_index = container.python; 276 auto range = py_index.equalRange(_self); 277 if (range.empty) { 278 auto count = py_index.insert(mapping); 279 enforce(count != 0, 280 format("could not add py reference %x for T=%s, t=%s", 281 _self, T.stringof, Mapping.DKey(t))); 282 }else{ 283 auto count = py_index.replace(PSR(range).front, mapping); 284 enforce(count != 0, 285 format("could not update py reference %x for T=%s, t=%s", 286 _self, T.stringof, Mapping.DKey(t))); 287 } 288 } 289 290 void remove_pyd_mapping(T)(PyObject* self) { 291 import std.range; 292 import std.stdio; 293 alias pyd_references!T.Mapping Mapping; 294 alias pyd_references!T.container container; 295 296 auto py_index = container.python; 297 auto range = py_index.equalRange(self); 298 if(!range.empty) { 299 py_index.remove(take(PSR(range),1)); 300 } 301 } 302 303 bool isConversionAddingFunctionAttributes( 304 uint fromTypeFunctionAttributes, 305 uint toTypeFunctionAttributes) { 306 return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0; 307 } 308 309 310 /** 311 * Returns the object contained in a Python wrapped type. 312 */ 313 T get_d_reference(T) (PyObject* _self) { 314 alias pyd_references!T.container container; 315 alias pyd_references!T.Mapping Mapping; 316 import thread = pyd.thread; 317 318 thread.ensureAttached(); 319 320 enforce(is_wrapped!T, 321 format( 322 "Error extracting D object: Type %s is not wrapped.", 323 typeid(T).toString())); 324 enforce(_self !is null, 325 "Error: trying to find D reference for null PyObject*!"); 326 327 auto py_index = container.python; 328 auto range = py_index.equalRange(_self); 329 330 enforce(!range.empty, 331 "Error extracting D object: reference not found!"); 332 // don't cast away type! 333 static if(is(T == class)) { 334 auto tif = cast(TypeInfo_Class) range.front.d_typeinfo; 335 auto t_info = typeid(Mapping.TypeInfoType!T); 336 bool found = false; 337 while(tif) { 338 if(t_info == tif) { 339 found = true; 340 break; 341 } 342 tif = tif.base; 343 } 344 345 enforce(found, 346 format( 347 "Type mismatch extracting D object: found: %s, required: %s", 348 range.front.d_typeinfo, 349 typeid(Mapping.TypeInfoType!T))); 350 351 }else{ 352 enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo, 353 format( 354 "Type mismatch extracting D object: found: %s, required: %s", 355 range.front.d_typeinfo, 356 typeid(Mapping.TypeInfoType!T))); 357 } 358 // don't cast away constness! 359 // mutable => const, etc, okay 360 enforce(constCompatible(range.front.constness, constness!T), 361 format( 362 "constness mismatch required: %s, found: %s", 363 constness_ToString(constness!T), 364 constness_ToString(range.front.constness))); 365 static if(isFunctionPointer!T || isDelegate!T) { 366 // don't cast away linkage! 367 enforce(range.front.linkage == functionLinkage!T, 368 format( 369 "trying to convert a extern(\"%s\") " ~ 370 "%s to extern(\"%s\")", 371 range.front.linkage, (isDelegate!T ? "delegate":"function"), 372 functionLinkage!T)); 373 // losing function attributes is ok, 374 // giving a function new ones, not so much 375 enforce( 376 !isConversionAddingFunctionAttributes( 377 range.front.functionAttributes, functionAttributes!T), 378 format( 379 "trying to convert %s%s to %s", 380 SetFunctionAttributes!(T, 381 functionLinkage!T, 382 FunctionAttribute.none).stringof, 383 attrs_to_string(range.front.functionAttributes), 384 T.stringof)); 385 } 386 387 return range.front.FromKey!T(); 388 } 389 390 /// If the passed D reference has an existing Python object, return a borrowed 391 /// reference to it. Otherwise, return null. 392 PyObject_BorrowedRef* get_python_reference(T) (T t) { 393 alias pyd_references!T.container container; 394 alias pyd_references!T.Mapping Mapping; 395 396 auto d_index = container.d; 397 auto range = d_index.equalRange(Mapping.DKey(t)); 398 if(range.empty) return null; 399 return borrowed(range.front.py); 400 } 401 402 /** 403 * Returns a new Python object of a wrapped type. 404 */ 405 // WrapPyObject_FromTypeAndObject 406 // WrapPyObject_FromObject 407 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) { 408 if (!type) { 409 type = &PydTypeObject!T; 410 } 411 if (is_wrapped!(T)) { 412 // If this object is already wrapped, get the existing object. 413 PyObject_BorrowedRef* obj_p = get_python_reference(t); 414 if (obj_p) { 415 return Py_INCREF(obj_p); 416 } 417 // Otherwise, allocate a new object 418 PyObject* obj = type.tp_new(type, null, null); 419 // Set the contained instance 420 set_pyd_mapping(obj, t); 421 return obj; 422 } else { 423 PyErr_SetString(PyExc_RuntimeError, 424 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr); 425 return null; 426 } 427 }