1 /** 2 This file provides `ScopedForeignCallback` to be used in every callback, and use to provide runtime initialization (now unused). 3 4 Copyright: Guillaume Piolat 2015-2016. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module dplug.core.runtime; 8 9 import core.runtime; 10 import core.atomic; 11 import core.stdc.stdlib; 12 13 import std.traits; 14 import std.functional: toDelegate; 15 16 import dplug.core.fpcontrol; 17 import dplug.core.nogc; 18 19 20 /// RAII struct to cover extern callbacks. 21 /// This only deals with CPU identification and FPU control words save/restore. 22 struct ScopedForeignCallback(bool dummyDeprecated, bool saveRestoreFPU) 23 { 24 public: 25 nothrow: 26 @nogc: 27 28 /// Thread that shouldn't be attached are eg. the audio threads. 29 void enter() 30 { 31 debug _entered = true; 32 33 static if (saveRestoreFPU) 34 _fpControl.initialize(); 35 } 36 37 ~this() 38 { 39 // Ensure enter() was called. 40 debug assert(_entered); 41 } 42 43 @disable this(this); 44 45 private: 46 47 static if (saveRestoreFPU) 48 FPControl _fpControl; 49 50 debug bool _entered = false; 51 } 52 53 /// "RUNTIME SECTION" 54 /// A function or method that can _use_ the runtime thanks to thread attachment. 55 /// 56 /// Runtime initialization must be dealt with externally with a `ScopedRuntime` struct 57 /// (typically long-lived since there can be only one). 58 /// 59 /// Warning: every GC object is reclaimed at the end of the runtime section. 60 /// By using GC.addRoot you can make GC object survive the collection. 61 /// 62 /// Returns: a callback Voldement inside which you can use the runtime, but you can't escape GC memory. 63 auto runtimeSection(F)(F functionOrDelegateThatCanBeGC) nothrow @nogc if (isCallable!(F)) 64 { 65 // turn that into a delegate for simplicity purposes 66 auto myGCDelegate = toDelegate(functionOrDelegateThatCanBeGC); 67 alias T = typeof(myGCDelegate); 68 69 static ReturnType!T internalFunc(T fun, Parameters!T params) nothrow 70 { 71 import core.stdc.stdio; 72 ScopedRuntimeSection section; 73 section.enter(); 74 75 // Important: we only support `nothrow` runtime section. 76 // Supporting exception was creating spurious bugs, probably the excpeption being collected. 77 return fun(params); 78 } 79 80 // We return this callable Voldemort type 81 82 static struct ManualDelegate 83 { 84 typeof(myGCDelegate.ptr) ptr; 85 typeof(myGCDelegate.funcptr) funcptr; 86 87 ReturnType!T opCall(Parameters!T params) nothrow @nogc 88 { 89 T dg; 90 dg.funcptr = funcptr; 91 dg.ptr = ptr; 92 return assumeNoGC(&internalFunc)(dg, params); 93 } 94 95 @disable this(this); 96 } 97 98 ManualDelegate fakeDg; 99 fakeDg.funcptr = myGCDelegate.funcptr; 100 fakeDg.ptr = myGCDelegate.ptr; 101 return fakeDg; 102 } 103 104 /// RAII struct for runtime initialization, to be used once by the plug-in client. 105 /// Without underlying `ScopedRuntime`, there can be no `runtimeSection`. 106 struct ScopedRuntime 107 { 108 public: 109 nothrow: 110 @nogc: 111 112 void initialize() 113 { 114 try 115 { 116 bool initOK = assumeNoGC(&Runtime.initialize)(); 117 118 if (!initOK) 119 assert(false, "Runtime initialization shouldn't fail"); 120 } 121 catch(Exception e) 122 { 123 assert(false, "Runtime initialization shouldn't fail"); 124 } 125 126 version(OSX) 127 { 128 atomicStore(_initialized, true); 129 } 130 else 131 { 132 _initialized = true; 133 } 134 } 135 136 ~this() 137 { 138 version(OSX){} 139 else 140 { 141 if (_initialized) 142 { 143 bool terminated; 144 try 145 { 146 terminated = assumeNoGC(&Runtime.terminate)(); 147 } 148 catch(Exception e) 149 { 150 terminated = false; 151 } 152 assert(terminated); 153 _initialized = false; 154 } 155 } 156 } 157 158 @disable this(this); 159 160 private: 161 162 version(OSX) 163 { 164 // Note: this is shared across plug-in instantiation 165 // TODO: use zero-init mutex once it exist 166 static shared(bool) _initialized = false; 167 } 168 else 169 { 170 bool _initialized = false; 171 } 172 } 173 174 private: 175 176 version(OSX) 177 { 178 version(DigitalMars) 179 { 180 // Workaround for DMDFE Issue #18456, can't use pragma(crt_destructor) with -lib 181 // See_also https://issues.dlang.org/show_bug.cgi?id=18456 182 // So we leak the runtime. 183 // this means GC objects' destructors won't be called (not that you should rely on this anyway. 184 } 185 else 186 { 187 // We need that new feature because on OSX shared libraries share their runtime and globals 188 // This is called at termination of the host program 189 extern(C) pragma(crt_destructor) void deactivateDRuntime() 190 { 191 import core.stdc.stdio; 192 193 bool initialized = atomicLoad(ScopedRuntime._initialized); 194 195 if (initialized) 196 { 197 try 198 { 199 bool terminated = assumeNoGC(&Runtime.terminate)(); 200 } 201 catch(Exception e) 202 { 203 } 204 atomicStore(ScopedRuntime._initialized, false); // TODO: this atomic is racey 205 } 206 } 207 } 208 } 209 210 211 212 /// RAII struct to ensure thread attacment is initialized and usable 213 /// => that allow to use GC, TLS etc in a single function. 214 /// This isn't meant to be used directly, and it should certainly only be used in a scoped 215 /// manner without letting a registered thread exit. 216 struct ScopedRuntimeSection 217 { 218 import core.thread: thread_attachThis, thread_detachThis; 219 220 public: 221 nothrow: 222 /// Thread that shouldn't be attached are eg. the audio threads. 223 void enter() 224 { 225 // shoud allow reentrant threads 226 bool alreadyAttached = isThisThreadAttached(); 227 if (!alreadyAttached) 228 { 229 try 230 { 231 thread_attachThis(); 232 } 233 catch(Exception e) 234 { 235 assert(false, "thread_attachThis is not supposed to fail"); 236 } 237 _threadWasAttached = true; 238 } 239 } 240 241 ~this() 242 { 243 // Detach current thread if it was attached by this runtime section 244 if (_threadWasAttached) 245 { 246 try 247 { 248 thread_detachThis(); 249 } 250 catch(Exception e) 251 { 252 assert(false, "thread_detachThis is not supposed to fail"); 253 } 254 _threadWasAttached = false; 255 } 256 257 // By collecting here we avoid correctness by coincidence for someone 258 // that would rely on things remaining valid out of the ScopedRuntimeSection 259 debug 260 { 261 import core.memory; 262 GC.collect(); 263 } 264 } 265 266 @disable this(this); 267 268 private: 269 270 bool _threadWasAttached = false; 271 272 static bool isThisThreadAttached() nothrow 273 { 274 import core.memory; 275 import core.thread; 276 GC.disable(); scope(exit) GC.enable(); 277 if (auto t = Thread.getThis()) 278 return true; 279 else 280 return false; 281 } 282 } 283 284 285