Skip to content

Adopt malloc_type for allocating Swift objects from runtime #63226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions include/swift/Runtime/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@
#error Masking ISAs are incompatible with opaque ISAs
#endif

#if defined(__APPLE__) && defined(__LP64__) && __has_include(<malloc_type_private.h>) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
# include <TargetConditionals.h>
# if TARGET_OS_IOS && !TARGET_OS_SIMULATOR
# define SWIFT_STDLIB_HAS_MALLOC_TYPE 1
# endif
#endif
#ifndef SWIFT_STDLIB_HAS_MALLOC_TYPE
# define SWIFT_STDLIB_HAS_MALLOC_TYPE 0
#endif

/// Which bits in the class metadata are used to distinguish Swift classes
/// from ObjC classes?
#ifndef SWIFT_CLASS_IS_SWIFT_MASK
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Runtime/Heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ namespace swift {
SWIFT_EXTERN_C SWIFT_RETURNS_NONNULL SWIFT_NODISCARD SWIFT_RUNTIME_EXPORT_ATTRIBUTE
void *swift_slowAlloc(size_t bytes, size_t alignMask);

using MallocTypeId = unsigned long long;

SWIFT_RETURNS_NONNULL SWIFT_NODISCARD
void *swift_slowAllocTyped(size_t bytes, size_t alignMask, MallocTypeId typeId);

// If the caller cannot promise to zero the object during destruction,
// then call these corresponding APIs:
SWIFT_RUNTIME_EXPORT
Expand Down
29 changes: 25 additions & 4 deletions stdlib/public/runtime/Heap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ static inline malloc_zone_t *DEFAULT_ZONE() {
// the use of AlignedAlloc. This allows manually allocated to memory to always
// be deallocated with AlignedFree without knowledge of its original allocation
// alignment.
//
static size_t computeAlignment(size_t alignMask) {
return (alignMask == ~(size_t(0))) ? _swift_MinAllocationAlignment
: alignMask + 1;
}

Comment on lines +76 to +80
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this helper to reduce code duplication. Also: relevant comment is now right next to the code.

// For alignMask > (_minAllocationAlignment-1)
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
// The runtime must use AlignedAlloc, and the standard library must
Expand All @@ -93,15 +97,32 @@ void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
p = malloc(size);
#endif
} else {
size_t alignment = (alignMask == ~(size_t(0)))
? _swift_MinAllocationAlignment
: alignMask + 1;
size_t alignment = computeAlignment(alignMask);
p = AlignedAlloc(size, alignment);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}

void *swift::swift_slowAllocTyped(size_t size, size_t alignMask,
MallocTypeId typeId) {
Comment on lines +107 to +108
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first, I had the calls to the old and new malloc APIs in one function and let the untyped one forward to the typed one:

void *swift_slowAlloc(size_t size, size_t alignMask) {
  return swift_slowAllocTyped(size, alignMask, /*typeId=*/0);
}

But the #if became very unwieldy, so I decided to turn it around (Typed variant doing it's thing and having the untyped one as a fallback) and accept a bit of code duplication.

#if SWIFT_STDLIB_HAS_MALLOC_TYPE
if (__builtin_available(macOS 9998, iOS 9998, tvOS 9998, watchOS 9998, *)) {
void *p;
// This check also forces "default" alignment to use malloc_memalign().
if (alignMask <= MALLOC_ALIGN_MASK) {
p = malloc_type_zone_malloc(DEFAULT_ZONE(), size, typeId);
} else {
size_t alignment = computeAlignment(alignMask);
p = malloc_type_zone_memalign(DEFAULT_ZONE(), alignment, size, typeId);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}
#endif
return swift_slowAlloc(size, alignMask);
}

// Unknown alignment is specified by passing alignMask == ~(size_t(0)), forcing
// the AlignedFree deallocation path for unknown alignment. The memory
// deallocated with unknown alignment must have been allocated with either
Expand Down
109 changes: 109 additions & 0 deletions stdlib/public/runtime/HeapObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
# include "swift/Runtime/ObjCBridge.h"
# include <dlfcn.h>
#endif
#if SWIFT_STDLIB_HAS_MALLOC_TYPE
# include <malloc_type_private.h>
#endif
#include "Leaks.h"

using namespace swift;
Expand Down Expand Up @@ -115,12 +118,118 @@ static HeapObject *_swift_tryRetain_(HeapObject *object)
return _ ## name ## _ args; \
} while(0)

#if SWIFT_STDLIB_HAS_MALLOC_TYPE
static malloc_type_summary_t
computeMallocTypeSummary(const HeapMetadata *heapMetadata) {
assert(isHeapMetadataKind(heapMetadata->getKind()));
auto *classMetadata = heapMetadata->getClassObject();
auto *typeDesc = heapMetadata->getTypeContextDescriptor();

malloc_type_summary_t summary = {};

// Objc
if (classMetadata && classMetadata->isPureObjC()) {
summary.type_kind = MALLOC_TYPE_KIND_OBJC;
return summary;
}

// Runtime internal and unclassified
if (!typeDesc) {
summary.type_kind = MALLOC_TYPE_KIND_CXX;
return summary;
}

// Swift
summary.type_kind = MALLOC_TYPE_KIND_SWIFT;

bool isGenericData = true;
for (auto &field : *typeDesc->Fields.get()) {
if (field.isIndirectCase()) {
isGenericData = false;
if (field.isVar())
summary.layout_semantics.data_pointer = true;
else
summary.layout_semantics.immutable_pointer = true;
}
}

if (classMetadata->Flags & ClassFlags::UsesSwiftRefcounting) {
summary.layout_semantics.reference_count = true;
} else {
summary.layout_semantics.generic_data = isGenericData;
}

return summary;

// FIXME: these are all the things we are potentially interested in
// typedef struct {
// bool data_pointer : 1;
// bool struct_pointer : 1;
// bool immutable_pointer : 1;
// bool anonymous_pointer : 1;
// bool reference_count : 1;
// bool resource_handle : 1;
// bool spatial_bounds : 1;
// bool tainted_data : 1;
// bool generic_data : 1;
// uint16_t unused : 7;
// } malloc_type_layout_semantics_t;
}

struct MallocTypeCacheEntry {
// union malloc_type_descriptor_t {
// struct {
// uint32_t hash;
// malloc_type_summary_t summary;
// };
// malloc_type_id_t type_id;
// };
malloc_type_descriptor_t desc;

friend llvm::hash_code hash_value(const MallocTypeCacheEntry &entry) {
return hash_value(entry.desc.hash);
}
bool matchesKey(uint32_t key) const { return desc.hash == key; }
};
static ConcurrentReadableHashMap<MallocTypeCacheEntry> MallocTypes;

static malloc_type_id_t getMallocTypeId(const HeapMetadata *heapMetadata) {
uint64_t metadataPtrBits = reinterpret_cast<uint64_t>(heapMetadata);
uint32_t key = (metadataPtrBits >> 32) ^ (metadataPtrBits >> 0);

{
auto snapshot = MallocTypes.snapshot();
if (auto *entry = snapshot.find(key))
return entry->desc.type_id;
}

malloc_type_descriptor_t desc = {
.hash = key,
.summary = computeMallocTypeSummary(heapMetadata)
};

MallocTypes.getOrInsert(
key, [desc](MallocTypeCacheEntry *entry, bool created) {
if (created)
entry->desc = desc;
return true;
});

return desc.type_id;
}
#endif // SWIFT_STDLIB_HAS_MALLOC_TYPE

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
#if SWIFT_STDLIB_HAS_MALLOC_TYPE
auto object = reinterpret_cast<HeapObject *>(swift_slowAllocTyped(
requiredSize, requiredAlignmentMask, getMallocTypeId(metadata)));
#else
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
#endif

// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
Expand Down