Files
swift-mirror/include/swift/Runtime/PrebuiltStringMap.h
Mike Ash 4b3a197dc2 [Runtime] Support type descriptor map in LibPrespecialized.
The descriptor map is keyed by a simplified mangling that canonicalizes the differences that we accept in _contextDescriptorMatchesMangling, such as the ability to specify any kind of type with an OtherNominalType node.

This simplified mangling is not necessarily unique, but we use _contextDescriptorMatchesMangling for the final equality checking when looking up entries in the map, so occasional collisions are acceptable and get resolved when probing the table.

The table is meant to be comprehensive, so it includes all descriptors that can be looked up by name, and a negative result means the descriptor does not exist in the shared cache. We add a flag to the options that can mark it as non-definitive in case we ever need to degrade this, and fall back to a full search after a negative result.

The map encompasses the entire shared cache but we need to reject lookups for types in images that aren't loaded. The map includes an image index which allows us to cheaply query whether a given descriptor is in a loaded image or not, so we can ignore ones which are not.

TypeMetadataPrivateState now has a separate sections array for sections within the shared cache. _searchTypeMetadataRecords consults the map first. If no result is found in the map and the map is marked as comprehensive, then only the sections outside the shared cache need to be scanned.

Replace the SWIFT_DEBUG_ENABLE_LIB_PRESPECIALIZED environment variable with one specifically for metadata and one for descriptor lookup so they can be controlled independently. Also add SWIFT_DEBUG_VALIDATE_LIB_PRESPECIALIZED_DESCRIPTOR_LOOKUP which consults the map and does the full scan, and ensures they produce the same result, for debugging purposes.

Enhance the environment variable code to track whether a variable was set at all. This allows SWIFT_DEBUG_ENABLE_LIB_PRESPECIALIZED to override the default in either direction.

Remove the disablePrespecializedMetadata global and instead modify the mapConfiguration to disable prespecialized metadata when an image is loaded that overrides one in the shared cache.

rdar://113059233
2024-08-01 18:43:15 -04:00

286 lines
9.2 KiB
C++

//===--- PrebuiltStringMap.h - Statically built string map ------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_PREBUILT_STRING_MAP_H
#define SWIFT_PREBUILT_STRING_MAP_H
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
namespace swift {
struct PrebuiltStringMapBase {
uint64_t arraySize;
/// Construct an empty map. Must be constructed in memory at least as large as
/// byteSize(arraySize). The map can hold at most arraySize-1 values.
/// Attempting to insert more than that will result in fatal errors when
/// inserting or retrieving values.
PrebuiltStringMapBase(uint64_t arraySize) : arraySize(arraySize) {}
// Based on MurmurHash2
uint64_t hash(const void *data, size_t len) const {
uint64_t magic = 0xc6a4a7935bd1e995ULL;
uint64_t salt = 47;
uint64_t hash = len * magic;
const uint8_t *cursor = (const uint8_t *)data;
const uint8_t *bulkEnd = cursor + (len & ~(sizeof(uint64_t) - 1));
size_t remaining = len;
while (cursor != bulkEnd) {
uint64_t value;
memcpy(&value, cursor, sizeof(uint64_t));
cursor += sizeof(uint64_t);
remaining -= sizeof(uint64_t);
value *= magic;
value ^= value >> salt;
value *= magic;
hash ^= value;
hash *= magic;
}
// This is never going to be false, but it's comforting.
static_assert(sizeof(uint64_t) == 8);
// Collect the last few bytes.
switch (remaining & 7) {
case 7:
hash ^= (uint64_t)cursor[6] << 48;
[[fallthrough]];
case 6:
hash ^= (uint64_t)cursor[5] << 40;
[[fallthrough]];
case 5:
hash ^= (uint64_t)cursor[4] << 32;
[[fallthrough]];
case 4:
hash ^= (uint64_t)cursor[3] << 24;
[[fallthrough]];
case 3:
hash ^= (uint64_t)cursor[2] << 16;
[[fallthrough]];
case 2:
hash ^= (uint64_t)cursor[1] << 8;
[[fallthrough]];
case 1:
hash ^= (uint64_t)cursor[0];
}
hash *= magic;
hash ^= hash >> salt;
hash *= magic;
hash ^= hash >> salt;
return hash;
}
/// Search for a matching entry in the map. `isMatch` is called with a
/// candidate index and returns true if there is a match at that index.
template <typename IsMatch>
std::optional<size_t> findIndex(const void *string, size_t len,
const IsMatch &isMatch) const {
uint64_t hashValue = hash(string, len);
size_t index = hashValue % arraySize;
size_t numSearched = 0;
while (!isMatch(index)) {
index = index + 1;
if (index >= arraySize)
index = 0;
numSearched++;
if (numSearched > arraySize) {
assert(false &&
"Could not find match in PrebuiltStringMapBase::findIndex");
return std::nullopt;
}
}
return index;
}
};
/// A map that can be pre-built out of process. Uses a fixed hash function with
/// no per-process seeding to ensure consistent hashes between builder and user.
///
/// The elements are tail allocated. `byteSize` can be used to calculate the
/// amount of memory needed. The memory must be initialized with all string
/// values set to null. StringTy is opaque for insertion, except for using the
/// provided stringIsNull function to check for null values.
template <typename StringTy, typename ElemTy, bool (*stringIsNull)(StringTy)>
struct PrebuiltStringMap : PrebuiltStringMapBase {
PrebuiltStringMap(uint64_t arraySize) : PrebuiltStringMapBase(arraySize) {}
struct ArrayElement {
StringTy key;
ElemTy value;
};
ArrayElement *array() {
uintptr_t start = (uintptr_t)(&arraySize + 1);
return (ArrayElement *)start;
}
const ArrayElement *array() const {
uintptr_t start = (uintptr_t)(&arraySize + 1);
return (ArrayElement *)start;
}
static size_t byteSize(uint64_t arraySize) {
return sizeof(PrebuiltStringMapBase) + sizeof(ArrayElement) * arraySize;
}
/// Perform the search portion of an insertion operation. Returns a pointer to
/// the element where string is to be inserted. The caller is responsible for
/// initializing the element to contain the string/value. It is assumed that
/// the key does not already exist in the map. If it does exist, this will
/// insert a useless duplicate.
ArrayElement *insert(const void *string, size_t len) {
auto foundIndex = findIndex(string, len, [&](size_t index) {
return stringIsNull(array()[index].key);
});
if (foundIndex)
return &array()[*foundIndex];
return nullptr;
}
ArrayElement *insert(const char *string) {
return insert(string, strlen(string));
}
/// Look up the given string in the table. Requires that StringTy be
/// `const char *`.
const ArrayElement *find(const char *toFind) const {
size_t len = strlen(toFind);
return find(toFind, len);
}
const ArrayElement *find(const char *toFind, size_t len) const {
auto equalOrNull = [&](size_t index) {
auto key = array()[index].key;
// NULL is considered a "match" as we want to stop the search on NULL too.
if (stringIsNull(key))
return true;
// key is NUL terminated but toFind may not be. Check that they have equal
// contents up to len, and check that key has a terminating NUL at the
// right point.
if (strncmp(key, toFind, len) == 0 && key[len] == 0)
return true;
// Not NULL, not equal, keep searching.
return false;
};
auto foundIndex = findIndex(toFind, len, equalOrNull);
if (!foundIndex)
return nullptr;
const auto &elementPtr = &array()[*foundIndex];
// If the "matching" element contains a NULL then we didn't find a match.
if (stringIsNull(elementPtr->key))
return nullptr;
return elementPtr;
}
};
/// A pre-built map with string-based keys that are implicit, i.e. equality can
/// be determined by looking at the values. The map contains auxiliary data
/// stored out of line from the main elements, to avoid padding when the aux
/// data is smaller than the alignment of the main elements.
template <typename ElemTy, typename AuxTy>
struct PrebuiltAuxDataImplicitStringMap : PrebuiltStringMapBase {
PrebuiltAuxDataImplicitStringMap(uint64_t arraySize)
: PrebuiltStringMapBase(arraySize) {}
static size_t byteSize(uint64_t arraySize) {
return sizeof(PrebuiltStringMapBase) + sizeof(ElemTy) * arraySize +
sizeof(AuxTy) * arraySize;
}
using DataPointers = std::pair<ElemTy *, AuxTy *>;
using DataPointersConst = std::pair<const ElemTy *, const AuxTy *>;
const ElemTy *elements() const { return (const ElemTy *)(&arraySize + 1); }
ElemTy *elements() { return (ElemTy *)(&arraySize + 1); }
const AuxTy *aux() const { return (const AuxTy *)(elements() + arraySize); }
AuxTy *aux() { return (AuxTy *)(elements() + arraySize); }
DataPointersConst pointers(size_t index) const {
return {&elements()[index], &aux()[index]};
}
DataPointers pointers(size_t index) {
return {&elements()[index], &aux()[index]};
}
/// Perform the search portion of an insertion operation. Returns pointers to
/// the element and aux data where the value is to be inserted. The caller is
/// responsible for initializing the element and aux data. It is assumed that
/// the key does not already exist in the map. If it does exist, this will
/// insert a duplicate.
///
/// isNull is a callable passed a pair of pointers to an element and
/// corresponding auxiliary data, and must return true if the element is
/// considered NULL (empty).
template <typename IsNull>
DataPointers insert(const char *string, const IsNull &isNull) {
auto foundIndex = findIndex(string, strlen(string), [&](size_t index) {
return isNull(pointers(index));
});
if (!foundIndex)
return {nullptr, nullptr};
return pointers(*foundIndex);
}
/// Look up the given key in the map.
///
/// isMatch is a callable passed a pair of pointers to the element and
/// auxiliary data, and must return true if the elements they point to are a
/// match for what's being looked up.
///
/// isNull must return true if the elements are NULL/empty, as with insert().
///
/// The returned pointers point to the matched element and auxiliary data, if
/// a match was found. They point to a NULL entry if no map was found. They
/// will only be NULL if the table data was malformed and no match or NULL
/// exists in it.
template <typename IsMatch, typename IsNull>
DataPointersConst find(const char *toFind, size_t len, const IsMatch &isMatch,
const IsNull &isNull) const {
auto foundIndex = findIndex(toFind, len, [&](size_t index) {
return isNull(pointers(index)) || isMatch(pointers(index));
});
if (!foundIndex)
return {nullptr, nullptr};
return pointers(*foundIndex);
}
};
} // namespace swift
#endif // SWIFT_PREBUILT_STRING_MAP_H