]>
Commit | Line | Data |
---|---|---|
8972963c A |
1 | /* |
2 | * Copyright (c) 2010 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | /*********************************************************************** | |
24 | * objc-block-trampolines.m | |
25 | * Author: b.bum | |
26 | * | |
27 | **********************************************************************/ | |
28 | ||
29 | /*********************************************************************** | |
30 | * Imports. | |
31 | **********************************************************************/ | |
32 | #include "objc-private.h" | |
33 | #include "runtime.h" | |
34 | ||
35 | #include <Block.h> | |
36 | #include <Block_private.h> | |
37 | #include <mach/mach.h> | |
38 | ||
39 | // symbols defined in assembly files | |
40 | // Don't use the symbols directly; they're thumb-biased on some ARM archs. | |
41 | #define TRAMP(tramp) \ | |
42 | static inline uintptr_t tramp(void) { \ | |
43 | extern void *_##tramp; \ | |
44 | return ((uintptr_t)&_##tramp) & ~1UL; \ | |
45 | } | |
46 | // Scalar return | |
47 | TRAMP(a1a2_tramphead); // trampoline header code | |
48 | TRAMP(a1a2_firsttramp); // first trampoline | |
8972963c A |
49 | TRAMP(a1a2_trampend); // after the last trampoline |
50 | ||
8070259c | 51 | #if SUPPORT_STRET |
8972963c A |
52 | // Struct return |
53 | TRAMP(a2a3_tramphead); | |
54 | TRAMP(a2a3_firsttramp); | |
8972963c | 55 | TRAMP(a2a3_trampend); |
8070259c | 56 | #endif |
8972963c A |
57 | |
58 | // argument mode identifier | |
59 | typedef enum { | |
60 | ReturnValueInRegisterArgumentMode, | |
8070259c | 61 | #if SUPPORT_STRET |
8972963c | 62 | ReturnValueOnStackArgumentMode, |
8070259c | 63 | #endif |
8972963c | 64 | |
8070259c | 65 | ArgumentModeCount |
8972963c A |
66 | } ArgumentMode; |
67 | ||
8972963c | 68 | |
8070259c A |
69 | // We must take care with our data layout on architectures that support |
70 | // multiple page sizes. | |
71 | // | |
72 | // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE. | |
73 | // On some platforms this requires additional linker flags. | |
74 | // | |
75 | // When we allocate a page pair, we use PAGE_MAX_SIZE size. | |
76 | // This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE. | |
77 | // | |
78 | // When we allocate a page pair, we use the process's page alignment. | |
79 | // This simplifies allocation because we don't need to force greater than | |
80 | // default alignment when running with small pages, but it also means | |
81 | // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK. | |
82 | ||
83 | struct TrampolineBlockPagePair | |
84 | { | |
85 | TrampolineBlockPagePair *nextPagePair; // linked list of all pages | |
86 | TrampolineBlockPagePair *nextAvailablePage; // linked list of pages with available slots | |
87 | ||
88 | uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available | |
89 | ||
90 | // Payload data: block pointers and free list. | |
91 | // Bytes parallel with trampoline header code are the fields above or unused | |
92 | // uint8_t blocks[ PAGE_MAX_SIZE - sizeof(TrampolineBlockPagePair) ] | |
8972963c | 93 | |
8972963c | 94 | // Code: trampoline header followed by trampolines. |
8070259c A |
95 | // uint8_t trampolines[PAGE_MAX_SIZE]; |
96 | ||
8972963c | 97 | // Per-trampoline block data format: |
8070259c | 98 | // initial value is 0 while page data is filled sequentially |
8972963c | 99 | // when filled, value is reference to Block_copy()d block |
8070259c A |
100 | // when empty, value is index of next available slot OR 0 if never used yet |
101 | ||
102 | union Payload { | |
103 | id block; | |
104 | uintptr_t nextAvailable; // free list | |
105 | }; | |
106 | ||
107 | static uintptr_t headerSize() { | |
108 | return (uintptr_t) (a1a2_firsttramp() - a1a2_tramphead()); | |
109 | } | |
110 | ||
111 | static uintptr_t slotSize() { | |
112 | return 8; | |
113 | } | |
8972963c | 114 | |
8070259c A |
115 | static uintptr_t startIndex() { |
116 | // headerSize is assumed to be slot-aligned | |
117 | return headerSize() / slotSize(); | |
118 | } | |
8972963c | 119 | |
8070259c A |
120 | static uintptr_t endIndex() { |
121 | return (uintptr_t)PAGE_MAX_SIZE / slotSize(); | |
122 | } | |
8972963c | 123 | |
8070259c A |
124 | static bool validIndex(uintptr_t index) { |
125 | return (index >= startIndex() && index < endIndex()); | |
126 | } | |
8972963c | 127 | |
8070259c A |
128 | Payload *payload(uintptr_t index) { |
129 | assert(validIndex(index)); | |
130 | return (Payload *)((char *)this + index*slotSize()); | |
131 | } | |
8972963c | 132 | |
8070259c A |
133 | IMP trampoline(uintptr_t index) { |
134 | assert(validIndex(index)); | |
135 | char *imp = (char *)this + index*slotSize() + PAGE_MAX_SIZE; | |
136 | #if __arm__ | |
137 | imp++; // trampoline is Thumb instructions | |
cd5f04f5 | 138 | #endif |
8070259c A |
139 | return (IMP)imp; |
140 | } | |
8972963c | 141 | |
8070259c A |
142 | uintptr_t indexForTrampoline(IMP tramp) { |
143 | uintptr_t tramp0 = (uintptr_t)this + PAGE_MAX_SIZE; | |
144 | uintptr_t start = tramp0 + headerSize(); | |
145 | uintptr_t end = tramp0 + PAGE_MAX_SIZE; | |
146 | uintptr_t address = (uintptr_t)tramp; | |
147 | if (address >= start && address < end) { | |
148 | return (uintptr_t)(address - tramp0) / slotSize(); | |
149 | } | |
150 | return 0; | |
151 | } | |
8972963c | 152 | |
8070259c A |
153 | static void check() { |
154 | assert(TrampolineBlockPagePair::slotSize() == 8); | |
155 | assert(TrampolineBlockPagePair::headerSize() >= sizeof(TrampolineBlockPagePair)); | |
156 | assert(TrampolineBlockPagePair::headerSize() % TrampolineBlockPagePair::slotSize() == 0); | |
157 | ||
158 | // _objc_inform("%p %p %p", a1a2_tramphead(), a1a2_firsttramp(), | |
159 | // a1a2_trampend()); | |
160 | assert(a1a2_tramphead() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE | |
161 | assert(a1a2_tramphead() + PAGE_MAX_SIZE == a1a2_trampend()); | |
162 | #if SUPPORT_STRET | |
163 | // _objc_inform("%p %p %p", a2a3_tramphead(), a2a3_firsttramp(), | |
164 | // a2a3_trampend()); | |
165 | assert(a2a3_tramphead() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE | |
166 | assert(a2a3_tramphead() + PAGE_MAX_SIZE == a2a3_trampend()); | |
167 | #endif | |
168 | ||
169 | #if __arm__ | |
170 | // make sure trampolines are Thumb | |
171 | extern void *_a1a2_firsttramp; | |
172 | extern void *_a2a3_firsttramp; | |
173 | assert(((uintptr_t)&_a1a2_firsttramp) % 2 == 1); | |
174 | assert(((uintptr_t)&_a2a3_firsttramp) % 2 == 1); | |
175 | #endif | |
176 | } | |
8972963c | 177 | |
8070259c | 178 | }; |
8972963c | 179 | |
8070259c A |
180 | // two sets of trampoline pages; one for stack returns and one for register returns |
181 | static TrampolineBlockPagePair *headPagePairs[ArgumentModeCount]; | |
8972963c | 182 | |
8070259c | 183 | #pragma mark Utility Functions |
8972963c A |
184 | |
185 | static inline void _lock() { | |
186 | #if __OBJC2__ | |
187 | rwlock_write(&runtimeLock); | |
188 | #else | |
189 | mutex_lock(&classLock); | |
190 | #endif | |
191 | } | |
192 | ||
193 | static inline void _unlock() { | |
194 | #if __OBJC2__ | |
195 | rwlock_unlock_write(&runtimeLock); | |
196 | #else | |
197 | mutex_unlock(&classLock); | |
198 | #endif | |
199 | } | |
200 | ||
201 | static inline void _assert_locked() { | |
202 | #if __OBJC2__ | |
203 | rwlock_assert_writing(&runtimeLock); | |
204 | #else | |
205 | mutex_assert_locked(&classLock); | |
206 | #endif | |
207 | } | |
208 | ||
209 | #pragma mark Trampoline Management Functions | |
8070259c A |
210 | static TrampolineBlockPagePair *_allocateTrampolinesAndData(ArgumentMode aMode) |
211 | { | |
8972963c A |
212 | _assert_locked(); |
213 | ||
214 | vm_address_t dataAddress; | |
215 | ||
8070259c | 216 | TrampolineBlockPagePair::check(); |
8972963c A |
217 | |
218 | TrampolineBlockPagePair *headPagePair = headPagePairs[aMode]; | |
219 | ||
220 | if (headPagePair) { | |
7257e56c | 221 | assert(headPagePair->nextAvailablePage == nil); |
8972963c A |
222 | } |
223 | ||
8070259c A |
224 | kern_return_t result; |
225 | for (int i = 0; i < 5; i++) { | |
226 | result = vm_allocate(mach_task_self(), &dataAddress, | |
227 | PAGE_MAX_SIZE * 2, | |
7257e56c | 228 | TRUE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION)); |
8972963c A |
229 | if (result != KERN_SUCCESS) { |
230 | mach_error("vm_allocate failed", result); | |
7257e56c | 231 | return nil; |
8972963c A |
232 | } |
233 | ||
8070259c A |
234 | vm_address_t codeAddress = dataAddress + PAGE_MAX_SIZE; |
235 | result = vm_deallocate(mach_task_self(), codeAddress, PAGE_MAX_SIZE); | |
8972963c A |
236 | if (result != KERN_SUCCESS) { |
237 | mach_error("vm_deallocate failed", result); | |
7257e56c | 238 | return nil; |
8972963c A |
239 | } |
240 | ||
241 | uintptr_t codePage; | |
242 | switch(aMode) { | |
243 | case ReturnValueInRegisterArgumentMode: | |
8070259c | 244 | codePage = a1a2_tramphead(); |
8972963c | 245 | break; |
8070259c | 246 | #if SUPPORT_STRET |
8972963c | 247 | case ReturnValueOnStackArgumentMode: |
8070259c | 248 | codePage = a2a3_tramphead(); |
8972963c | 249 | break; |
8070259c | 250 | #endif |
8972963c A |
251 | default: |
252 | _objc_fatal("unknown return mode %d", (int)aMode); | |
253 | break; | |
254 | } | |
255 | vm_prot_t currentProtection, maxProtection; | |
8070259c A |
256 | result = vm_remap(mach_task_self(), &codeAddress, PAGE_MAX_SIZE, |
257 | 0, FALSE, mach_task_self(), codePage, TRUE, | |
258 | ¤tProtection, &maxProtection, VM_INHERIT_SHARE); | |
8972963c | 259 | if (result != KERN_SUCCESS) { |
8070259c A |
260 | result = vm_deallocate(mach_task_self(), |
261 | dataAddress, PAGE_MAX_SIZE); | |
8972963c A |
262 | if (result != KERN_SUCCESS) { |
263 | mach_error("vm_deallocate for retry failed.", result); | |
7257e56c | 264 | return nil; |
8972963c | 265 | } |
8070259c | 266 | } else { |
8972963c | 267 | break; |
8070259c | 268 | } |
8972963c A |
269 | } |
270 | ||
8070259c | 271 | if (result != KERN_SUCCESS) { |
7257e56c | 272 | return nil; |
8070259c | 273 | } |
8972963c A |
274 | |
275 | TrampolineBlockPagePair *pagePair = (TrampolineBlockPagePair *) dataAddress; | |
8070259c | 276 | pagePair->nextAvailable = pagePair->startIndex(); |
7257e56c A |
277 | pagePair->nextPagePair = nil; |
278 | pagePair->nextAvailablePage = nil; | |
8972963c A |
279 | |
280 | if (headPagePair) { | |
8070259c A |
281 | TrampolineBlockPagePair *lastPagePair = headPagePair; |
282 | while(lastPagePair->nextPagePair) | |
283 | lastPagePair = lastPagePair->nextPagePair; | |
8972963c | 284 | |
8070259c | 285 | lastPagePair->nextPagePair = pagePair; |
8972963c A |
286 | headPagePairs[aMode]->nextAvailablePage = pagePair; |
287 | } else { | |
288 | headPagePairs[aMode] = pagePair; | |
289 | } | |
290 | ||
291 | return pagePair; | |
292 | } | |
293 | ||
8070259c A |
294 | static TrampolineBlockPagePair * |
295 | _getOrAllocatePagePairWithNextAvailable(ArgumentMode aMode) | |
296 | { | |
8972963c A |
297 | _assert_locked(); |
298 | ||
299 | TrampolineBlockPagePair *headPagePair = headPagePairs[aMode]; | |
300 | ||
301 | if (!headPagePair) | |
302 | return _allocateTrampolinesAndData(aMode); | |
303 | ||
8070259c A |
304 | // make sure head page is filled first |
305 | if (headPagePair->nextAvailable != headPagePair->endIndex()) | |
8972963c A |
306 | return headPagePair; |
307 | ||
308 | if (headPagePair->nextAvailablePage) // check if there is a page w/a hole | |
309 | return headPagePair->nextAvailablePage; | |
310 | ||
311 | return _allocateTrampolinesAndData(aMode); // tack on a new one | |
312 | } | |
313 | ||
8070259c A |
314 | static TrampolineBlockPagePair * |
315 | _pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex, | |
316 | TrampolineBlockPagePair **outHeadPagePair) | |
317 | { | |
8972963c A |
318 | _assert_locked(); |
319 | ||
8070259c A |
320 | for (int arg = 0; arg < ArgumentModeCount; arg++) { |
321 | for (TrampolineBlockPagePair *pagePair = headPagePairs[arg]; | |
322 | pagePair; | |
323 | pagePair = pagePair->nextPagePair) | |
324 | { | |
325 | uintptr_t index = pagePair->indexForTrampoline(anImp); | |
326 | if (index) { | |
327 | if (outIndex) *outIndex = index; | |
328 | if (outHeadPagePair) *outHeadPagePair = headPagePairs[arg]; | |
8972963c A |
329 | return pagePair; |
330 | } | |
8972963c A |
331 | } |
332 | } | |
333 | ||
7257e56c | 334 | return nil; |
8972963c A |
335 | } |
336 | ||
8070259c A |
337 | |
338 | static ArgumentMode | |
339 | _argumentModeForBlock(id block) | |
340 | { | |
341 | ArgumentMode aMode = ReturnValueInRegisterArgumentMode; | |
342 | ||
343 | #if SUPPORT_STRET | |
344 | if (_Block_has_signature(block) && _Block_use_stret(block)) | |
345 | aMode = ReturnValueOnStackArgumentMode; | |
346 | #else | |
347 | assert(! (_Block_has_signature(block) && _Block_use_stret(block))); | |
348 | #endif | |
349 | ||
350 | return aMode; | |
351 | } | |
352 | ||
353 | ||
8972963c | 354 | // `block` must already have been copied |
8070259c A |
355 | IMP |
356 | _imp_implementationWithBlockNoCopy(id block) | |
8972963c A |
357 | { |
358 | _assert_locked(); | |
359 | ||
8070259c A |
360 | ArgumentMode aMode = _argumentModeForBlock(block); |
361 | ||
362 | TrampolineBlockPagePair *pagePair = | |
363 | _getOrAllocatePagePairWithNextAvailable(aMode); | |
8972963c A |
364 | if (!headPagePairs[aMode]) |
365 | headPagePairs[aMode] = pagePair; | |
366 | ||
8070259c A |
367 | uintptr_t index = pagePair->nextAvailable; |
368 | assert(index >= pagePair->startIndex() && index < pagePair->endIndex()); | |
369 | TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); | |
370 | ||
371 | uintptr_t nextAvailableIndex = payload->nextAvailable; | |
372 | if (nextAvailableIndex == 0) { | |
373 | // First time through (unused slots are zero). Fill sequentially. | |
374 | // If the page is now full this will now be endIndex(), handled below. | |
375 | nextAvailableIndex = index + 1; | |
376 | } | |
377 | pagePair->nextAvailable = nextAvailableIndex; | |
378 | if (nextAvailableIndex == pagePair->endIndex()) { | |
379 | // PagePair is now full (free list or wilderness exhausted) | |
380 | // Remove from available page linked list | |
381 | TrampolineBlockPagePair *iterator = headPagePairs[aMode]; | |
382 | while(iterator && (iterator->nextAvailablePage != pagePair)) { | |
383 | iterator = iterator->nextAvailablePage; | |
384 | } | |
385 | if (iterator) { | |
386 | iterator->nextAvailablePage = pagePair->nextAvailablePage; | |
7257e56c | 387 | pagePair->nextAvailablePage = nil; |
8972963c | 388 | } |
8972963c A |
389 | } |
390 | ||
8070259c A |
391 | payload->block = block; |
392 | return pagePair->trampoline(index); | |
8972963c A |
393 | } |
394 | ||
8972963c A |
395 | |
396 | #pragma mark Public API | |
cd5f04f5 | 397 | IMP imp_implementationWithBlock(id block) |
8972963c A |
398 | { |
399 | block = Block_copy(block); | |
400 | _lock(); | |
8070259c | 401 | IMP returnIMP = _imp_implementationWithBlockNoCopy(block); |
8972963c A |
402 | _unlock(); |
403 | return returnIMP; | |
404 | } | |
405 | ||
406 | ||
cd5f04f5 | 407 | id imp_getBlock(IMP anImp) { |
8070259c | 408 | uintptr_t index; |
8972963c A |
409 | TrampolineBlockPagePair *pagePair; |
410 | ||
7257e56c | 411 | if (!anImp) return nil; |
8972963c A |
412 | |
413 | _lock(); | |
414 | ||
8070259c | 415 | pagePair = _pageAndIndexContainingIMP(anImp, &index, nil); |
8972963c A |
416 | |
417 | if (!pagePair) { | |
418 | _unlock(); | |
7257e56c | 419 | return nil; |
8972963c | 420 | } |
8070259c A |
421 | |
422 | TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); | |
8972963c | 423 | |
8070259c A |
424 | if (payload->nextAvailable <= TrampolineBlockPagePair::endIndex()) { |
425 | // unallocated | |
8972963c | 426 | _unlock(); |
7257e56c | 427 | return nil; |
8972963c A |
428 | } |
429 | ||
430 | _unlock(); | |
431 | ||
8070259c | 432 | return payload->block; |
8972963c A |
433 | } |
434 | ||
435 | BOOL imp_removeBlock(IMP anImp) { | |
436 | TrampolineBlockPagePair *pagePair; | |
437 | TrampolineBlockPagePair *headPagePair; | |
8070259c | 438 | uintptr_t index; |
8972963c A |
439 | |
440 | if (!anImp) return NO; | |
441 | ||
442 | _lock(); | |
8070259c | 443 | pagePair = _pageAndIndexContainingIMP(anImp, &index, &headPagePair); |
8972963c A |
444 | |
445 | if (!pagePair) { | |
446 | _unlock(); | |
447 | return NO; | |
448 | } | |
8070259c A |
449 | |
450 | TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); | |
451 | id block = payload->block; | |
8972963c A |
452 | // block is released below |
453 | ||
8070259c A |
454 | payload->nextAvailable = pagePair->nextAvailable; |
455 | pagePair->nextAvailable = index; | |
8972963c A |
456 | |
457 | // make sure this page is on available linked list | |
458 | TrampolineBlockPagePair *pagePairIterator = headPagePair; | |
459 | ||
8070259c A |
460 | // see if page is the next available page for any existing pages |
461 | while (pagePairIterator->nextAvailablePage && | |
462 | pagePairIterator->nextAvailablePage != pagePair) | |
463 | { | |
8972963c | 464 | pagePairIterator = pagePairIterator->nextAvailablePage; |
8070259c | 465 | } |
8972963c | 466 | |
8070259c A |
467 | if (! pagePairIterator->nextAvailablePage) { |
468 | // if iteration stopped because nextAvail was nil | |
8972963c A |
469 | // add to end of list. |
470 | pagePairIterator->nextAvailablePage = pagePair; | |
7257e56c | 471 | pagePair->nextAvailablePage = nil; |
8972963c A |
472 | } |
473 | ||
474 | _unlock(); | |
475 | Block_release(block); | |
476 | return YES; | |
477 | } |