Skip to content

Commit 96555ba

Browse files
committed
[Runtime] Add pointer-based lookup of prespecialized metadata.
The existing lookup uses a hash table with the type's mangled name as the key. Building that key is costly. Add a new table that uses pointer keys, so that we can use the descriptor and arguments directly as the key. rdar://127621414
1 parent 66e3110 commit 96555ba

File tree

2 files changed

+213
-23
lines changed

2 files changed

+213
-23
lines changed

include/swift/Runtime/LibPrespecialized.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,25 @@ struct LibPrespecializedData {
3232

3333
TargetPointer<Runtime, const void> metadataMap;
3434
TargetPointer<Runtime, const void> disabledProcessesTable;
35+
TargetPointer<Runtime, const void> pointerKeyedMetadataMap;
36+
37+
typename Runtime::StoredSize optionFlags;
38+
39+
// Existing fields are above, add new fields below this point.
3540

3641
static constexpr uint32_t currentMajorVersion = 1;
37-
static constexpr uint32_t currentMinorVersion = 2;
42+
static constexpr uint32_t currentMinorVersion = 3;
3843

3944
static constexpr uint32_t minorVersionWithDisabledProcessesTable = 2;
45+
static constexpr uint32_t minorVersionWithPointerKeyedMetadataMap = 3;
46+
static constexpr uint32_t minorVersionWithOptionFlags = 3;
47+
48+
// Option flags values.
49+
enum : typename Runtime::StoredSize {
50+
// When this flag is set, the runtime should default to using the
51+
// pointer-keyed table. When not set, default to using the name-keyed table.
52+
OptionFlagDefaultToPointerKeyedMap = 1ULL << 0,
53+
};
4054

4155
// Helpers for retrieving the metadata map in-process.
4256
static bool stringIsNull(const char *str) { return str == nullptr; }
@@ -52,6 +66,18 @@ struct LibPrespecializedData {
5266
return nullptr;
5367
return reinterpret_cast<const char *const *>(disabledProcessesTable);
5468
}
69+
70+
const void *getPointerKeyedMetadataMap() const {
71+
if (minorVersion < minorVersionWithPointerKeyedMetadataMap)
72+
return nullptr;
73+
return pointerKeyedMetadataMap;
74+
}
75+
76+
typename Runtime::StoredSize getOptionFlags() const {
77+
if (minorVersion < minorVersionWithOptionFlags)
78+
return 0;
79+
return optionFlags;
80+
}
5581
};
5682

5783
const LibPrespecializedData<InProcess> *getLibPrespecializedData();

stdlib/public/runtime/LibPrespecialized.cpp

Lines changed: 186 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
#include <mach-o/dyld_priv.h>
2929
#endif
3030

31+
#if __has_include(<os/feature_private.h>)
32+
#include <os/feature_private.h> // for os_feature_enabled_simple()
33+
#define HAS_OS_FEATURE 1
34+
#endif
35+
3136
using namespace swift;
3237

3338
static std::atomic<bool> disablePrespecializedMetadata = false;
@@ -171,12 +176,20 @@ struct LibPrespecializedState {
171176
struct AddressRange {
172177
uintptr_t start, end;
173178

174-
bool contains(const void *ptr) {
179+
bool contains(const void *ptr) const {
175180
return start <= (uintptr_t)ptr && (uintptr_t)ptr < end;
176181
}
177182
};
178183

184+
enum class MapConfiguration {
185+
UseNameKeyedMap,
186+
UsePointerKeyedMap,
187+
UsePointerKeyedMapDebugMode,
188+
Disabled,
189+
};
190+
179191
const LibPrespecializedData<InProcess> *data;
192+
MapConfiguration mapConfiguration;
180193
AddressRange sharedCacheRange{0, 0};
181194
AddressRange metadataAllocatorInitialPoolRange{0, 0};
182195

@@ -197,6 +210,60 @@ struct LibPrespecializedState {
197210
metadataAllocatorInitialPoolRange.end =
198211
metadataAllocatorInitialPoolRange.start + initialPoolLength;
199212
#endif
213+
214+
// Must do this after the shared cache range has been retrieved.
215+
mapConfiguration = computeMapConfiguration(data);
216+
}
217+
218+
MapConfiguration
219+
computeMapConfiguration(const LibPrespecializedData<InProcess> *data) {
220+
// If no data, we have to disable.
221+
if (!data)
222+
return MapConfiguration::Disabled;
223+
224+
auto nameKeyedMap = data->getMetadataMap();
225+
auto pointerKeyedMap = data->getPointerKeyedMetadataMap();
226+
227+
// If we don't have either map, then disable it completely.
228+
if (!nameKeyedMap && !pointerKeyedMap) {
229+
LOG("No prespecializations map available from data at %p, disabling.",
230+
data);
231+
return MapConfiguration::Disabled;
232+
}
233+
234+
// If we don't have the pointer-keyed map, fall back to the name-keyed map.
235+
if (!pointerKeyedMap) {
236+
LOG("Data at %p only contains name-keyed map.", data);
237+
return MapConfiguration::UseNameKeyedMap;
238+
}
239+
240+
// If we don't have the name-keyed map, always use the pointer-keyed map.
241+
if (!nameKeyedMap) {
242+
LOG("Data at %p only contains pointer-keyed map.", data);
243+
return MapConfiguration::UsePointerKeyedMap;
244+
}
245+
246+
// We have both. Consult the option flag.
247+
bool usePointerKeyedMap =
248+
data->getOptionFlags() &
249+
LibPrespecializedData<InProcess>::OptionFlagDefaultToPointerKeyedMap;
250+
251+
#if HAS_OS_FEATURE
252+
if (os_feature_enabled_simple(Swift, useAlternatePrespecializationMap,
253+
false))
254+
usePointerKeyedMap = !usePointerKeyedMap;
255+
#endif
256+
257+
LOG("Data at %p contains both maps. Using %s keyed map.", data,
258+
usePointerKeyedMap ? "pointer" : "name");
259+
if (usePointerKeyedMap) {
260+
// If we're using a map outside the shared cache, then we're in debug mode
261+
// and need to use our own slow lookup.
262+
if (!sharedCacheRange.contains(pointerKeyedMap))
263+
return MapConfiguration::UsePointerKeyedMapDebugMode;
264+
return MapConfiguration::UsePointerKeyedMap;
265+
}
266+
return MapConfiguration::UseNameKeyedMap;
200267
}
201268
};
202269

@@ -218,7 +285,7 @@ hasNonTypeGenericArguments(const TargetGenericContext<InProcess> *generics) {
218285
}
219286

220287
static bool
221-
isPotentialPrespecializedPointer(LibPrespecializedState &prespecialized,
288+
isPotentialPrespecializedPointer(const LibPrespecializedState &state,
222289
const void *pointer) {
223290
// Prespecialized metadata descriptors and arguments are always in the shared
224291
// cache. They're either statically emitted metadata, or they're
@@ -228,16 +295,16 @@ isPotentialPrespecializedPointer(LibPrespecializedState &prespecialized,
228295
// If we're loading a debug libprespecialized, we can't do these checks, so
229296
// just say everything is a potential argument. Performance is not so
230297
// important in that case.
231-
if (!prespecialized.sharedCacheRange.contains(prespecialized.data))
298+
if (!state.sharedCacheRange.contains(state.data))
232299
return true;
233300

234301
// Anything outside the shared cache isn't a potential argument.
235-
if (!prespecialized.sharedCacheRange.contains(pointer))
302+
if (!state.sharedCacheRange.contains(pointer))
236303
return false;
237304

238305
// Dynamically allocated metadata could be within the shared cache, in the
239306
// initial metadata allocation pool. Reject anything in that region.
240-
if (prespecialized.metadataAllocatorInitialPoolRange.contains(pointer))
307+
if (state.metadataAllocatorInitialPoolRange.contains(pointer))
241308
return false;
242309

243310
return true;
@@ -256,20 +323,10 @@ swift::libPrespecializedImageLoaded() {
256323
#endif
257324
}
258325

259-
Metadata *
260-
swift::getLibPrespecializedMetadata(const TypeContextDescriptor *description,
261-
const void *const *arguments) {
262-
if (SWIFT_UNLIKELY(
263-
disableForValidation
264-
|| disablePrespecializedMetadata.load(std::memory_order_acquire)))
265-
return nullptr;
266-
267-
auto &prespecialized = LibPrespecialized.get();
268-
269-
auto *data = prespecialized.data;
270-
if (!data)
271-
return nullptr;
272-
326+
static Metadata *
327+
getMetadataFromNameKeyedMap(const LibPrespecializedState &state,
328+
const TypeContextDescriptor *description,
329+
const void *const *arguments) {
273330
auto *generics = description->getGenericContext();
274331
if (!generics)
275332
return nullptr;
@@ -279,15 +336,15 @@ swift::getLibPrespecializedMetadata(const TypeContextDescriptor *description,
279336
if (hasNonTypeGenericArguments(generics))
280337
return nullptr;
281338

282-
if (!isPotentialPrespecializedPointer(prespecialized, description)) {
339+
if (!isPotentialPrespecializedPointer(state, description)) {
283340
LOG("Rejecting descriptor %p, not in the shared cache",
284341
(const void *)description);
285342
return nullptr;
286343
}
287344

288345
auto numKeyArguments = generics->getGenericContextHeader().NumKeyArguments;
289346
for (unsigned i = 0; i < numKeyArguments; i++) {
290-
if (!isPotentialPrespecializedPointer(prespecialized, arguments[i])) {
347+
if (!isPotentialPrespecializedPointer(state, arguments[i])) {
291348
LOG("Rejecting argument %u %p to descriptor %p, not in the shared cache",
292349
i, arguments[i], (const void *)description);
293350
return nullptr;
@@ -322,13 +379,120 @@ swift::getLibPrespecializedMetadata(const TypeContextDescriptor *description,
322379
}
323380

324381
auto key = mangling.result();
325-
auto *metadataMap = data->getMetadataMap();
382+
auto *metadataMap = state.data->getMetadataMap();
326383
auto *element = metadataMap->find(key.data(), key.size());
327384
auto *result = element ? element->value : nullptr;
328385
LOG("found %p for key '%.*s'.", result, (int)key.size(), key.data());
329386
return result;
330387
}
331388

389+
static Metadata *
390+
getMetadataFromPointerKeyedMap(const LibPrespecializedState &state,
391+
const TypeContextDescriptor *description,
392+
const void *const *arguments) {
393+
#if DYLD_FIND_POINTER_HASH_TABLE_ENTRY_DEFINED
394+
auto *generics = description->getGenericContext();
395+
if (!generics)
396+
return nullptr;
397+
398+
auto argumentCount = generics->getGenericContextHeader().NumKeyArguments;
399+
400+
auto *map = state.data->getPointerKeyedMetadataMap();
401+
auto result = _dyld_find_pointer_hash_table_entry(
402+
map, description, argumentCount, const_cast<const void **>(arguments));
403+
LOG("Looking up description %p in dyld table, found %p.", description,
404+
result);
405+
return reinterpret_cast<Metadata *>(const_cast<void *>(result));
406+
#else
407+
LOG("Looking up description %p but dyld hash table call not available.",
408+
description);
409+
return nullptr;
410+
#endif
411+
}
412+
413+
// When we have a pointer-keyed map from a debug library, it's not built as a
414+
// hash table. We just scan it linearly.
415+
static Metadata *getMetadataFromPointerKeyedMapDebugMode(
416+
const LibPrespecializedState &state,
417+
const TypeContextDescriptor *description, const void *const *arguments) {
418+
auto *generics = description->getGenericContext();
419+
if (!generics)
420+
return nullptr;
421+
422+
auto argumentCount = generics->getGenericContextHeader().NumKeyArguments;
423+
auto *mapPtr = state.data->getPointerKeyedMetadataMap();
424+
425+
struct MapKey {
426+
size_t count;
427+
void *pointers[];
428+
};
429+
430+
struct MapEntry {
431+
const MapKey *key;
432+
Metadata *value;
433+
};
434+
435+
struct Map {
436+
size_t count;
437+
MapEntry entries[];
438+
};
439+
440+
const Map *map = reinterpret_cast<const Map *>(mapPtr);
441+
for (size_t i = 0; i < map->count; i++) {
442+
auto &entry = map->entries[i];
443+
444+
// Keys are descriptor followed by arguments, so their count is 1 plus the
445+
// argument count.
446+
if (entry.key->count != argumentCount + 1)
447+
continue;
448+
449+
// Check the descriptor.
450+
if (description != entry.key->pointers[0])
451+
continue;
452+
453+
// Check the rest. The pointers array is now offset by 1 since index 0 is
454+
// the descriptor.
455+
bool equal = true;
456+
for (size_t j = 0; j < argumentCount; j++) {
457+
if (entry.key->pointers[j + 1] != arguments[j]) {
458+
equal = false;
459+
break;
460+
}
461+
}
462+
463+
if (equal) {
464+
LOG("Looking up description %p in debug table, found %p.", description,
465+
entry.value);
466+
return entry.value;
467+
}
468+
}
469+
470+
LOG("Looking up description %p in debug table, no entry found.", description);
471+
return nullptr;
472+
}
473+
474+
Metadata *
475+
swift::getLibPrespecializedMetadata(const TypeContextDescriptor *description,
476+
const void *const *arguments) {
477+
if (SWIFT_UNLIKELY(disableForValidation || disablePrespecializedMetadata.load(
478+
std::memory_order_acquire)))
479+
return nullptr;
480+
481+
auto &state = LibPrespecialized.get();
482+
483+
switch (state.mapConfiguration) {
484+
case LibPrespecializedState::MapConfiguration::Disabled:
485+
return nullptr;
486+
case LibPrespecializedState::MapConfiguration::UseNameKeyedMap:
487+
return getMetadataFromNameKeyedMap(state, description, arguments);
488+
case LibPrespecializedState::MapConfiguration::UsePointerKeyedMap:
489+
return getMetadataFromPointerKeyedMap(state, description, arguments);
490+
case LibPrespecializedState::MapConfiguration::UsePointerKeyedMapDebugMode:
491+
return getMetadataFromPointerKeyedMapDebugMode(state, description,
492+
arguments);
493+
}
494+
}
495+
332496
void _swift_validatePrespecializedMetadata() {
333497
auto *data = getLibPrespecializedData();
334498
if (!data) {

0 commit comments

Comments
 (0)