1 /**
2  * Copyright: Copyright Auburn Sounds 2015-2016.
3  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4  * Authors:   Guillaume Piolat
5  */
6 module dplug.core.runtime;
7 
8 import dplug.core.fpcontrol;
9 import dplug.core.nogc;
10 import dplug.core.cpuid;
11 
12 // Helpers to deal with the D runtime.
13 version = useShakyWorkaround;
14 
15 /// When this version is defined, the runtime won't be initialized.
16 version = doNotUseRuntime;
17 
18 version(OSX)
19 {
20     __gshared bool didInitRuntime = false;
21 
22     version(useShakyWorkaround)
23     {
24         // Workaround Issue #15060
25         // https://issues.dlang.org/show_bug.cgi?id=15060
26         // Found by Martin Nowak and bitwise
27         // Trade-off a crash for a leak :|
28 
29         extern(C) int sysctlbyname(const char *, void *, size_t *, void *, size_t);
30 
31         alias dyld_image_states = int;
32         enum : dyld_image_states
33         {
34             dyld_image_state_initialized = 50,
35         }        
36 
37         extern(C) nothrow
38         {
39             alias dyld_image_state_change_handler = const(char)* function(dyld_image_states state, uint infoCount, void* dyld_image_info);
40         }
41 
42         extern(C) const(char)* ignoreImageLoad(dyld_image_states state, uint infoCount, void* dyld_image_info) nothrow
43         {
44             return null;
45         }
46 
47         extern(C) void dyld_register_image_state_change_handler(dyld_image_states state, bool batch, dyld_image_state_change_handler handler);
48     }
49 
50     // Initializes the runtime if not already initialized
51     void runtimeInitWorkaround15060()
52     {
53         import core.runtime;
54 
55         if(!didInitRuntime) // There is a race here, could it possibly be a problem? Until now, no.
56         {
57             Runtime.initialize();
58 
59             version(useShakyWorkaround)
60             {
61                 dyld_register_image_state_change_handler(dyld_image_state_initialized, false, &ignoreImageLoad);
62             }
63 
64             didInitRuntime = true;
65         }
66     }
67 }
68 
69 /// RAII struct to cover callbacks that need attachment and runtime initialized.
70 /// This deals with runtime inialization and thread attachment in a very explicit way.
71 struct ScopedForeignCallback(bool assumeRuntimeIsAlreadyInitialized,
72                              bool saveRestoreFPU)
73 {
74 public:
75 
76 
77     // On Windows, we can assume that the runtime is initialized already by virtue of DLL_PROCESS_ATTACH
78     version(Windows)
79         enum bool doInitializeRuntime = false;
80     else
81         enum bool doInitializeRuntime = !assumeRuntimeIsAlreadyInitialized;
82 
83     // Detaching threads when going out of callbacks.
84     // This avoid the GC pausing threads that are doing their things or have died since.
85     // This fixed #110 (Cubase + OS X), at a runtime cost.
86     enum bool detachThreadsAfterCallback = true;
87 
88     /// Thread that shouldn't be attached are eg. the audio threads.
89     void enter()
90     {
91         debug _entered = true;
92 
93         static if (saveRestoreFPU)
94             _fpControl.initialize();
95 
96         version(doNotUseRuntime)
97         {
98             // Just detect the CPU
99             initializeCpuid();
100         }
101         else
102         {
103             // Runtime initialization if needed.
104             static if (doInitializeRuntime)
105             {
106                 version(OSX)
107                     runtimeInitWorkaround15060();
108                 else
109                 {
110                     import core.runtime;
111                     Runtime.initialize();
112                 }
113 
114                 // CPUID detection
115                 initializeCpuid();
116             }
117 
118             import core.thread: thread_attachThis;
119 
120             static if (detachThreadsAfterCallback)
121             {
122                 bool alreadyAttached = isThisThreadAttached();
123                 if (!alreadyAttached)
124                 {
125                     thread_attachThis();
126                     _threadWasAttached = true;
127                 }
128             }
129             else
130             {
131                 thread_attachThis();
132             }
133         }
134     }
135 
136     ~this()
137     {
138         version(doNotUseRuntime)
139         {
140             // Nothing to do, since thread was never attached
141         }
142         else
143         {
144             static if (detachThreadsAfterCallback)
145                 if (_threadWasAttached)
146                 {
147                     import core.thread: thread_detachThis;
148                     thread_detachThis();
149                 }
150         }
151 
152         // Ensure enter() was called.
153         debug assert(_entered);
154     }
155 
156     @disable this(this);
157 
158 private:
159 
160     version(doNotUseRuntime)
161     {
162     }
163     else
164     {
165         static if (detachThreadsAfterCallback)
166             bool _threadWasAttached = false;
167     }
168 
169     static if (saveRestoreFPU)
170         FPControl _fpControl;
171 
172     debug bool _entered = false;
173 
174     static bool isThisThreadAttached() nothrow
175     {
176         import core.memory;
177         import core.thread;
178         GC.disable(); scope(exit) GC.enable();
179         if (auto t = Thread.getThis())
180             return true;
181         else
182             return false;
183     }
184 
185 }