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 26 import deimos.python.Python; 27 import util.multi_index; 28 import util.typeinfo; 29 30 // s/wrapped_class_object!T/PyObject/ 31 32 /** 33 * A useful check for whether a given class has been wrapped. Mainly used by 34 * the conversion functions (see make_object.d), but possibly useful elsewhere. 35 */ 36 template is_wrapped(T) { 37 alias pyd_references!T.Mapping Mapping; 38 39 static if(!is(Mapping.TypeInfoType!T == T)) { 40 alias is_wrapped!(Mapping.TypeInfoType!T) is_wrapped; 41 }else{ 42 bool is_wrapped = false; 43 } 44 } 45 46 /// _ 47 // wrapped_class_type 48 template PydTypeObject(T) { 49 alias pyd_references!T.Mapping Mapping; 50 51 static if(!is(Mapping.TypeInfoType!T == T)) { 52 alias PydTypeObject!(Mapping.TypeInfoType!T) PydTypeObject; 53 }else{ 54 // The type object, an instance of PyType_Type 55 PyTypeObject PydTypeObject; 56 } 57 } 58 59 template IsRefParam(alias p) { 60 enum IsRefParam = p == ParameterStorageClass.ref_ || 61 p == ParameterStorageClass.out_; 62 } 63 64 template FnHasRefParams(Fn) { 65 alias anySatisfy!(IsRefParam, ParameterStorageClassTuple!(Fn)) 66 FnHasRefParams; 67 } 68 69 struct DFn_Py_Mapping { 70 const(void)* d; 71 PyObject* py; 72 TypeInfo d_typeinfo; 73 TypeInfo[] params_info; 74 uint functionAttributes; 75 string linkage; 76 Constness constness; 77 78 this(Fn)(Fn d, PyObject* py) if(isFunctionPointer!Fn) { 79 static assert(!FnHasRefParams!Fn, 80 "Pyd cannot handle ref or out parameters at this time"); 81 this.d = DKey(d); 82 this.d_typeinfo = typeid(TypeInfoType!Fn); 83 this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Fn)) 84 .elements; 85 this.py = py; 86 this.functionAttributes = .functionAttributes!Fn; 87 this.linkage = functionLinkage!Fn; 88 this.constness = .constness!Fn; 89 } 90 91 template TypeInfoType(T) if(isFunctionPointer!T) { 92 alias Unqual!( 93 SetFunctionAttributes!(T, functionLinkage!T, 94 FunctionAttribute.none)) TypeInfoType; 95 } 96 97 public static const(void)* DKey(T)(T t) 98 if(isFunctionPointer!T) { 99 return cast(const(void)*) t; 100 } 101 102 public T FromKey(T)() { 103 return cast(T) this.d; 104 } 105 } 106 107 struct DDg_Py_Mapping { 108 const(void)*[2] d; 109 PyObject* py; 110 TypeInfo d_typeinfo; 111 TypeInfo[] params_info; 112 uint functionAttributes; 113 string linkage; 114 Constness constness; 115 116 this(Dg)(Dg d, PyObject* py) if(isDelegate!Dg) { 117 static assert(!FnHasRefParams!Dg, 118 "Pyd cannot handle ref or out parameters at this time"); 119 this.d = DKey(d); 120 this.d_typeinfo = typeid(TypeInfoType!Dg); 121 this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Dg)) 122 .elements; 123 this.py = py; 124 this.functionAttributes = .functionAttributes!Dg; 125 this.linkage = functionLinkage!Dg; 126 this.constness = .constness!Dg; 127 } 128 129 template TypeInfoType(T) { 130 alias Unqual!( 131 SetFunctionAttributes!(T, functionLinkage!T, 132 FunctionAttribute.none)) TypeInfoType; 133 } 134 135 public static const(void)*[2] DKey(T)(T t) 136 if(isDelegate!T) { 137 typeof(return) key; 138 key[0] = cast(const(void)*) t.ptr; 139 key[1] = cast(const(void)*) t.funcptr; 140 return key; 141 } 142 143 public T FromKey(T)() { 144 T x; 145 x.ptr = cast(void*) d[0]; 146 x.funcptr = cast(typeof(x.funcptr)) d[1]; 147 return x; 148 } 149 } 150 151 struct DStruct_Py_Mapping { 152 const(void)* d; 153 PyObject* py; 154 TypeInfo d_typeinfo; 155 Constness constness; 156 157 this(S)(S d, PyObject* py) if(isPointer!S && 158 is(pointerTarget!S == struct)) { 159 this.d = DKey(d); 160 this.d_typeinfo = typeid(TypeInfoType!S); 161 this.py = py; 162 this.constness = .constness!S; 163 } 164 165 template TypeInfoType(T) if(isPointer!T && is(pointerTarget!T == struct)) { 166 alias Unqual!T TypeInfoType; 167 } 168 169 public static const(void)* DKey(T)(T t) 170 if(isPointer!T && is(pointerTarget!T == struct)) { 171 return cast(const(void)*) t; 172 } 173 174 public T FromKey(T)() { 175 return cast(T) this.d; 176 } 177 } 178 179 struct DClass_Py_Mapping { 180 const(void)* d; 181 PyObject* py; 182 TypeInfo d_typeinfo; 183 Constness constness; 184 185 this(S)(S d, PyObject* py) if(is(S == class)) { 186 this.d = cast(const(void)*) d; 187 this.d_typeinfo = typeid(TypeInfoType!S); 188 this.py = py; 189 this.constness = .constness!S; 190 } 191 192 template TypeInfoType(T) if(is(T == class)) { 193 alias Unqual!T TypeInfoType; 194 } 195 196 public static const(void)* DKey(T)(T t) 197 if(is(T == class)) { 198 return cast(const(void)*) t; 199 } 200 201 public T FromKey(T)() { 202 return cast(T) this.d; 203 } 204 } 205 206 /// A bidirectional mapping of a pyobject and the d object associated to it. 207 /// On the python side, these are weak references; we don't want to prevent 208 /// python from reclaiming objects it is finished with. As such, on the D side, 209 /// if you take PyObject*s out of here and store them for an extended time 210 /// elsewhere, be sure to increment the reference count. 211 /// On the D side, we have strong references, but that is incidental to the GC. 212 /// If you stick d objects not allocated with the GC, there will probably be 213 /// leaks. 214 /// We use malloc for the container's structure because we can't use the GC 215 /// inside a destructor and we need to use this container there. 216 template reference_container(Mapping) { 217 alias MultiIndexContainer!(Mapping, IndexedBy!( 218 HashedUnique!("a.d"), 219 HashedUnique!("a.py")), 220 MallocAllocator, MutableView) 221 Container; 222 Container _reference_container = null; 223 224 @property reference_container() { 225 if(!_reference_container) { 226 _reference_container = new Container(); 227 Py_AtExit(&clear); 228 } 229 return _reference_container; 230 } 231 232 extern(C) void clear() { 233 if(_reference_container) { 234 _reference_container.get_index!0 .clear(); 235 } 236 } 237 } 238 239 // A mapping of all GC references that are being held by Python. 240 template pyd_references(T) { 241 static if(isDelegate!T) { 242 alias DDg_Py_Mapping Mapping; 243 }else static if (isFunctionPointer!T) { 244 alias DFn_Py_Mapping Mapping; 245 }else static if(isPointer!T && is(pointerTarget!T == struct)) { 246 alias DStruct_Py_Mapping Mapping; 247 }else static if (is(T == class)) { 248 alias DClass_Py_Mapping Mapping; 249 }else static assert(0, format("type %s cannot sent to pyd, because ??", 250 T.stringof)); 251 alias reference_container!Mapping container; 252 } 253 254 void set_pyd_mapping(T) (PyObject* _self, T t) { 255 import std.stdio; 256 alias pyd_references!T.Mapping Mapping; 257 alias pyd_references!T.container container; 258 259 Mapping mapping = Mapping(t, _self); 260 auto py_index = container.get_index!1; 261 auto range = py_index.equalRange(_self); 262 if (range.empty) { 263 auto count = py_index.insert(mapping); 264 enforce(count != 0, 265 format("could not add py reference %x for T=%s, t=%s", 266 _self, T.stringof, Mapping.DKey(t))); 267 }else{ 268 auto count = py_index.replace(PSR(range).front, mapping); 269 enforce(count != 0, 270 format("could not update py reference %x for T=%s, t=%s", 271 _self, T.stringof, Mapping.DKey(t))); 272 } 273 } 274 275 void remove_pyd_mapping(T)(PyObject* self) { 276 import std.range; 277 import std.stdio; 278 alias pyd_references!T.Mapping Mapping; 279 alias pyd_references!T.container container; 280 281 auto py_index = container.get_index!1; 282 auto range = py_index.equalRange(self); 283 if(!range.empty) { 284 py_index.remove(take(PSR(range),1)); 285 } 286 } 287 288 289 /** 290 * Returns the object contained in a Python wrapped type. 291 */ 292 T get_d_reference(T) (PyObject* _self) { 293 alias pyd_references!T.container container; 294 alias pyd_references!T.Mapping Mapping; 295 import thread = pyd.thread; 296 297 thread.ensureAttached(); 298 299 enforce(is_wrapped!T, 300 format( 301 "Error extracting D object: Type %s is not wrapped.", 302 typeid(T).toString())); 303 enforce(_self !is null, 304 "Error: trying to find D reference for null PyObject*!"); 305 306 auto py_index = container.get_index!1; 307 auto range = py_index.equalRange(_self); 308 309 enforce(!range.empty, 310 "Error extracting D object: reference not found!"); 311 // don't cast away type! 312 static if(is(T == class)) { 313 auto tif = cast(TypeInfo_Class) range.front.d_typeinfo; 314 auto t_info = typeid(Mapping.TypeInfoType!T); 315 bool found = false; 316 while(tif) { 317 if(t_info == tif) { 318 found = true; 319 break; 320 } 321 tif = tif.base; 322 } 323 324 enforce(found, 325 format( 326 "Type mismatch extracting D object: found: %s, required: %s", 327 range.front.d_typeinfo, 328 typeid(Mapping.TypeInfoType!T))); 329 330 }else{ 331 enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo, 332 format( 333 "Type mismatch extracting D object: found: %s, required: %s", 334 range.front.d_typeinfo, 335 typeid(Mapping.TypeInfoType!T))); 336 } 337 // don't cast away constness! 338 // mutable => const, etc, okay 339 enforce(constCompatible(range.front.constness, constness!T), 340 format( 341 "constness mismatch required: %s, found: %s", 342 constness_ToString(constness!T), 343 constness_ToString(range.front.constness))); 344 static if(isFunctionPointer!T || isDelegate!T) { 345 // don't cast away linkage! 346 enforce(range.front.linkage == functionLinkage!T, 347 format( 348 "trying to convert a extern(\"%s\") " 349 "%s to extern(\"%s\")", 350 range.front.linkage, (isDelegate!T ? "delegate":"function"), 351 functionLinkage!T)); 352 // losing function attributes is ok, 353 // giving a function new ones, not so much 354 enforce( 355 (~range.front.functionAttributes & functionAttributes!T) == 0, 356 format( 357 "trying to convert %s%s to %s", 358 SetFunctionAttributes!(T, 359 functionLinkage!T, 360 FunctionAttribute.none).stringof, 361 attrs_to_string(range.front.functionAttributes), 362 T.stringof)); 363 } 364 365 return range.front.FromKey!T(); 366 } 367 368 /// If the passed D reference has an existing Python object, return a borrowed 369 /// reference to it. Otherwise, return null. 370 PyObject_BorrowedRef* get_python_reference(T) (T t) { 371 alias pyd_references!T.container container; 372 alias pyd_references!T.Mapping Mapping; 373 374 auto d_index = container.get_index!0; 375 auto range = d_index.equalRange(Mapping.DKey(t)); 376 if(range.empty) return null; 377 return borrowed(range.front.py); 378 } 379 380 /** 381 * Returns a new Python object of a wrapped type. 382 */ 383 // WrapPyObject_FromTypeAndObject 384 // WrapPyObject_FromObject 385 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) { 386 if (!type) { 387 type = &PydTypeObject!T; 388 } 389 if (is_wrapped!(T)) { 390 // If this object is already wrapped, get the existing object. 391 PyObject_BorrowedRef* obj_p = get_python_reference(t); 392 if (obj_p) { 393 return Py_INCREF(obj_p); 394 } 395 // Otherwise, allocate a new object 396 PyObject* obj = type.tp_new(type, null, null); 397 // Set the contained instance 398 set_pyd_mapping(obj, t); 399 return obj; 400 } else { 401 PyErr_SetString(PyExc_RuntimeError, 402 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr); 403 return null; 404 } 405 } 406