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 alias MultiIndexContainer!(Mapping, IndexedBy!( 221 HashedUnique!("a.d"), "d", 222 HashedUnique!("a.py"), "python"), 223 MallocAllocator, MutableView) 224 Container; 225 Container _reference_container = null; 226 227 @property reference_container() { 228 if(!_reference_container) { 229 _reference_container = new Container(); 230 Py_AtExit(&clear); 231 } 232 return _reference_container; 233 } 234 235 extern(C) void clear() { 236 if(_reference_container) { 237 _reference_container.d.clear(); 238 } 239 } 240 } 241 242 // A mapping of all GC references that are being held by Python. 243 template pyd_references(T) { 244 static if(isDelegate!T) { 245 alias DDg_Py_Mapping Mapping; 246 }else static if (isFunctionPointer!T) { 247 alias DFn_Py_Mapping Mapping; 248 }else static if(isPointer!T && is(PointerTarget!T == struct)) { 249 alias DStruct_Py_Mapping Mapping; 250 }else static if (is(T == class)) { 251 alias DClass_Py_Mapping Mapping; 252 }else static assert(0, format("type %s cannot sent to pyd, because ??", 253 T.stringof)); 254 alias reference_container!Mapping container; 255 } 256 257 void set_pyd_mapping(T) (PyObject* _self, T t) { 258 import std.stdio; 259 alias pyd_references!T.Mapping Mapping; 260 alias pyd_references!T.container container; 261 262 Mapping mapping = Mapping(t, _self); 263 auto py_index = container.python; 264 auto range = py_index.equalRange(_self); 265 if (range.empty) { 266 auto count = py_index.insert(mapping); 267 enforce(count != 0, 268 format("could not add py reference %x for T=%s, t=%s", 269 _self, T.stringof, Mapping.DKey(t))); 270 }else{ 271 auto count = py_index.replace(PSR(range).front, mapping); 272 enforce(count != 0, 273 format("could not update py reference %x for T=%s, t=%s", 274 _self, T.stringof, Mapping.DKey(t))); 275 } 276 } 277 278 void remove_pyd_mapping(T)(PyObject* self) { 279 import std.range; 280 import std.stdio; 281 alias pyd_references!T.Mapping Mapping; 282 alias pyd_references!T.container container; 283 284 auto py_index = container.python; 285 auto range = py_index.equalRange(self); 286 if(!range.empty) { 287 py_index.remove(take(PSR(range),1)); 288 } 289 } 290 291 bool isConversionAddingFunctionAttributes( 292 uint fromTypeFunctionAttributes, 293 uint toTypeFunctionAttributes) { 294 return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0; 295 } 296 297 298 /** 299 * Returns the object contained in a Python wrapped type. 300 */ 301 T get_d_reference(T) (PyObject* _self) { 302 alias pyd_references!T.container container; 303 alias pyd_references!T.Mapping Mapping; 304 import thread = pyd.thread; 305 306 thread.ensureAttached(); 307 308 enforce(is_wrapped!T, 309 format( 310 "Error extracting D object: Type %s is not wrapped.", 311 typeid(T).toString())); 312 enforce(_self !is null, 313 "Error: trying to find D reference for null PyObject*!"); 314 315 auto py_index = container.python; 316 auto range = py_index.equalRange(_self); 317 318 enforce(!range.empty, 319 "Error extracting D object: reference not found!"); 320 // don't cast away type! 321 static if(is(T == class)) { 322 auto tif = cast(TypeInfo_Class) range.front.d_typeinfo; 323 auto t_info = typeid(Mapping.TypeInfoType!T); 324 bool found = false; 325 while(tif) { 326 if(t_info == tif) { 327 found = true; 328 break; 329 } 330 tif = tif.base; 331 } 332 333 enforce(found, 334 format( 335 "Type mismatch extracting D object: found: %s, required: %s", 336 range.front.d_typeinfo, 337 typeid(Mapping.TypeInfoType!T))); 338 339 }else{ 340 enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo, 341 format( 342 "Type mismatch extracting D object: found: %s, required: %s", 343 range.front.d_typeinfo, 344 typeid(Mapping.TypeInfoType!T))); 345 } 346 // don't cast away constness! 347 // mutable => const, etc, okay 348 enforce(constCompatible(range.front.constness, constness!T), 349 format( 350 "constness mismatch required: %s, found: %s", 351 constness_ToString(constness!T), 352 constness_ToString(range.front.constness))); 353 static if(isFunctionPointer!T || isDelegate!T) { 354 // don't cast away linkage! 355 enforce(range.front.linkage == functionLinkage!T, 356 format( 357 "trying to convert a extern(\"%s\") " ~ 358 "%s to extern(\"%s\")", 359 range.front.linkage, (isDelegate!T ? "delegate":"function"), 360 functionLinkage!T)); 361 // losing function attributes is ok, 362 // giving a function new ones, not so much 363 enforce( 364 !isConversionAddingFunctionAttributes( 365 range.front.functionAttributes, functionAttributes!T), 366 format( 367 "trying to convert %s%s to %s", 368 SetFunctionAttributes!(T, 369 functionLinkage!T, 370 FunctionAttribute.none).stringof, 371 attrs_to_string(range.front.functionAttributes), 372 T.stringof)); 373 } 374 375 return range.front.FromKey!T(); 376 } 377 378 /// If the passed D reference has an existing Python object, return a borrowed 379 /// reference to it. Otherwise, return null. 380 PyObject_BorrowedRef* get_python_reference(T) (T t) { 381 alias pyd_references!T.container container; 382 alias pyd_references!T.Mapping Mapping; 383 384 auto d_index = container.d; 385 auto range = d_index.equalRange(Mapping.DKey(t)); 386 if(range.empty) return null; 387 return borrowed(range.front.py); 388 } 389 390 /** 391 * Returns a new Python object of a wrapped type. 392 */ 393 // WrapPyObject_FromTypeAndObject 394 // WrapPyObject_FromObject 395 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) { 396 if (!type) { 397 type = &PydTypeObject!T; 398 } 399 if (is_wrapped!(T)) { 400 // If this object is already wrapped, get the existing object. 401 PyObject_BorrowedRef* obj_p = get_python_reference(t); 402 if (obj_p) { 403 return Py_INCREF(obj_p); 404 } 405 // Otherwise, allocate a new object 406 PyObject* obj = type.tp_new(type, null, null); 407 // Set the contained instance 408 set_pyd_mapping(obj, t); 409 return obj; 410 } else { 411 PyErr_SetString(PyExc_RuntimeError, 412 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr); 413 return null; 414 } 415 } 416