- no limits to external semaphore table
[cogvm:blessed.git] / platforms / Cross / vm / sqExternalSemaphores.c
1 /* sqExternalSemaphores.c
2  *      Cross-platform thread-safe external semaphore signalling.
3  *
4  *      Authors: Eliot Miranda & Brad Fowlow
5  *
6  *      Copyright (C) 2009 by Teleplace, Inc.
7  *
8  *      All rights reserved.
9  *   
10  *   This file is part of Squeak.
11  * 
12  *   Permission is hereby granted, free of charge, to any person obtaining a
13  *   copy of this software and associated documentation files (the "Software"),
14  *   to deal in the Software without restriction, including without limitation
15  *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  *   and/or sell copies of the Software, and to permit persons to whom the
17  *   Software is furnished to do so, subject to the following conditions:
18  * 
19  *   The above copyright notice and this permission notice shall be included in
20  *   all copies or substantial portions of the Software.
21  * 
22  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23  *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25  *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  *   DEALINGS IN THE SOFTWARE.
29  */
30
31 #include "sq.h"
32 #include "sqAssert.h"
33 #include "sqAtomicOps.h"
34 #include "sqMemoryFence.h"
35
36 /* This implements "lock-free" signalling of external semaphores where there is
37  * no lock between the signal responder (the VM) and signal requestors, but
38  * there may be spin-locking between signal requestors, depending on the implem-
39  * entation of atomicAddConst.
40  *
41  * Freedom from locks is very helpful in making the QAudioPlugin function on
42  * linux, where the absence of thread priorities for non-setuid programs means
43  * we cannot run the QAudioPlugin's ticker in a separate thread and must instead
44  * derive it from the interval-timer based signal-driven (software interrupt)
45  * heartbeat.  If the external semaphore state is locked while the VM is
46  * responding to external semaphore signal requests and the heartbeat interrupts
47  * causing a locking request for an external semaphore signal request then the
48  * system will deadlock, since the interrupt occurs in the VM thread.
49  *
50  * Lock freedom is achieved by having an array of request counters, and an array
51  * of response counters, one per external semaphore index.  To request a signal
52  * the requests are locked and the relevant request is incremented.  To respond
53  * to a request the VM increments the corresponding response until it matches
54  * the request, signalling the associated semaphore on each increment.
55  */
56
57 #if !COGMTVM
58 sqOSThread ioVMThread; /* initialized in the various <plat>/vm/sqFooMain.c */
59 #endif
60 extern void forceInterruptCheck(void);
61 extern sqInt doSignalSemaphoreWithIndex(sqInt semaIndex);
62
63 static volatile int sigIndexLow = 0;
64 static volatile int sigIndexHigh = 0;
65 static volatile int sigLock = 0;
66
67
68 /* do we need to dynamically grow the queue?  */
69 #define SQ_DYNAMIC_QUEUE_SIZE 0
70
71 /* a hardcoded limit for static-sized queue */
72 #define SQ_SIGNAL_QUEUE_SIZE 512
73
74 #if SQ_DYNAMIC_QUEUE_SIZE
75     static int maxPendingSignals = 0;
76     static volatile sqInt * signalQueue = 0;
77 #else
78     #define maxPendingSignals SQ_SIGNAL_QUEUE_SIZE
79     static sqInt signalQueue [maxPendingSignals];
80 #endif
81
82 static inline int hasPendingSignals(void) {
83     return sigIndexLow != sigIndexHigh;
84 }
85
86 static inline int maxQueueSize(void) {
87     return maxPendingSignals;
88 }
89
90 /* return a successive index in queue, wrap around in round-robin style */ 
91 static inline int succIndex(int index) {
92     return ((index + 1) == maxQueueSize()) ? 0 : index + 1; 
93 }
94
95
96
97
98 static inline void lockSignalQueue()
99 {
100     volatile int old;
101     /* spin to obtain a lock */
102     
103     do {
104         sqLowLevelMFence();        
105         sqCompareAndSwapRes(sigLock, 0, 1, old );
106     } while (old != 0);
107     
108 }
109
110 static inline void unlockSignalQueue() {
111     sigLock = 0;
112 }
113
114 /* Setting this at any time other than start-up can potentially lose requests.
115  * i.e. during the realloc new storage is allocated, the old contents are copied
116  * and then pointersd are switched.  Requests occurring during copying won't
117  * be seen if they occur to indices already copied.
118  * We could make this safer in the linux case by disabling interrupts, but
119  * there is little point.  The intended use is to set the table to some adequate
120  * maximum at start-up and avoid locking altogether.
121  */
122
123 void ioGrowSignalQueue( int n ) {
124 #if SQ_DYNAMIC_QUEUE_SIZE // ignore, if queue size is static
125
126     /* only to grow */
127     if (maxPendingSignals < n) {
128                 extern sqInt highBit(sqInt);
129                 int sz = 1 << highBit(n-1);
130                 assert(sz >= n);
131                 
132         
133         sqInt * newBuf = realloc(signalQueue, sz * sizeof(sqInt));
134
135         /* we should lock queue when growing, so nobody can access the queue buffer when we mutate the pointer */
136         lockSignalQueue();
137         signalQueue = newBuf;
138         unlockSignalQueue();
139         
140                 maxPendingSignals = sz;
141         }
142 #endif
143 }
144
145
146 void
147 ioInitExternalSemaphores(void)
148 {
149     ioGrowSignalQueue(SQ_SIGNAL_QUEUE_SIZE);
150 }
151
152 /* Signal the external semaphore with the given index.  Answer non-zero on
153  * success, zero otherwise.  This function is (should be) thread-safe;
154  * multiple threads may attempt to signal the same semaphore without error.
155  * An index of zero should be and is silently ignored.
156  *
157  *  (sig) As well as negative index.
158  */
159 sqInt
160 signalSemaphoreWithIndex(sqInt index)
161 {
162     int next;
163     
164         /* An index of zero should be and is silently ignored. */
165     if (index <=0)
166         return 0;
167     
168     /* we must use the locking semantics to avoid ABA problem on writing a semaphore index to queue,
169      so there is no chance for fetching thread to observe queue in inconsistent state.
170      */
171     lockSignalQueue();
172     
173     /* check for queue overflow */
174     next = succIndex(sigIndexHigh);
175     if (next == sigIndexLow ) {
176         
177 #if SQ_DYNAMIC_QUEUE_SIZE 
178         // grow and retry
179         unlockSignalQueue();
180         ioGrowSignalQueue( maxPendingSignals + 100);
181         return signalSemaphoreWithIndex(index);
182
183 #else
184         unlockSignalQueue();
185         // error if queue size is static  (perhaps better would be to sleep for a while and retry?)
186         error("External semaphore signal queue overflow");
187 #endif
188     }
189
190     signalQueue[sigIndexHigh] = index;
191     /* make sure semaphore index is written before we advance sigIndexHigh */
192     sqLowLevelMFence();
193     
194     sigIndexHigh = next;
195     /* reset lock */
196
197     unlockSignalQueue();
198     forceInterruptCheck();
199         return 1;
200 }
201
202
203 static inline sqInt fetchQueueItem()
204 {
205     // if queue size is dynamic, we should lock while accessing signalQueue
206     #if SQ_DYNAMIC_QUEUE_SIZE
207         lockSignalQueue();
208     #endif
209     sqInt result = signalQueue[sigIndexLow];
210     #if SQ_DYNAMIC_QUEUE_SIZE
211         unlockSignalQueue();
212     #endif
213     return result;
214 }
215
216
217
218 /* Signal any external semaphores for which signal request(s) are pending in queue.
219  * Answer whether a context switch occurred.
220  * sigIndexHigh may advance during processing, which is not big deal,
221  * since we flushing the queue anyways.
222  */
223
224 sqInt
225 doSignalExternalSemaphores(int externalSemaphoreTableSize)
226 {
227     int switched = 0;
228  
229     while (hasPendingSignals()) {
230         if(doSignalSemaphoreWithIndex(fetchQueueItem()))
231             switched = 1;
232         sigIndexLow = succIndex(sigIndexLow);
233     }    
234         return switched;
235 }
236
237
238 #if FOR_SQUEAK_VM_TESTS
239 /* see e.g. tests/sqExternalSemaphores/unixmain.c */
240 int
241 allRequestsAreAnswered(int externalSemaphoreTableSize)
242 {
243     /* yes, it is, otherwise we will get a queue overflow error ;) */
244         return 1;
245 }
246 #endif /* FOR_SQUEAK_VM_TESTS */
247
248
249
250 /* Following two are left for compatibility with language side */
251
252 void ioSetMaxExtSemTableSize(int n) { 
253     /* just ignore */
254 }
255
256 int ioGetMaxExtSemTableSize(void) { 
257     // answer an arbitrary large number.. to not confuse image side about it
258     return 10000000;
259     /*    return maxQueueSize(); */
260 }
261
262