Skip to content

Commit

Permalink
Implement Runtime Attributes in Reflection
Browse files Browse the repository at this point in the history
add docs

fix error

Fix linux and windows build

initialize the lock value
  • Loading branch information
Azoy committed Jan 25, 2023
1 parent fa1f536 commit 886e68b
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 59 deletions.
113 changes: 113 additions & 0 deletions stdlib/public/Reflection/Sources/Reflection/Attribute.swift
@@ -0,0 +1,113 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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
//
//===----------------------------------------------------------------------===//

import Swift
import _Runtime

/// A namespace used for working with runtime attributes.
@available(SwiftStdlib 5.9, *)
@frozen
public enum Attribute {
/// Get all the instances of a runtime attribute wherever it's attached to.
///
/// Example:
///
/// @runtimeMetadata
/// struct Field {
/// let name: String
///
/// init<T, U>(attachedTo: KeyPath<T, U>, _ name: String) {
/// self.name = name
/// }
/// }
///
/// struct Dog {
/// @Field("dog_breed")
/// let breed: String
/// }
///
/// let fields = Attribute.allInstances(of: Field.self)
///
/// for field in fields {
/// print(field.name) // "dog_breed"
/// }
///
/// - Parameters:
/// - type: The type of the attribute that is attached to various sources.
/// - Returns: A sequence of attribute instances of `type` in no particular
/// order.
@available(SwiftStdlib 5.9, *)
public static func allInstances<T>(of type: T.Type) -> AttributeInstances<T> {
let meta = Metadata(T.self)

guard meta.kind == .struct ||
meta.kind == .enum ||
meta.kind == .class else {
return AttributeInstances([])
}

return ImageInspection.withAttributeCache {
let attrDescriptor = meta.type.descriptor.base

guard let fnPtrs = $0[attrDescriptor] else {
return AttributeInstances([])
}

return AttributeInstances(fnPtrs)
}
}
}

/// A sequence wrapper over some runtime attribute instances.
///
/// Instances of `AttributeInstances` are created with the
/// `Attribute.allInstances(of:)` function.
@available(SwiftStdlib 5.9, *)
@frozen
public struct AttributeInstances<T> {
@usableFromInline
let fnPtrs: [UnsafeRawPointer]

@usableFromInline
var index = 0

@available(SwiftStdlib 5.9, *)
init(_ fnPtrs: [UnsafeRawPointer]) {
self.fnPtrs = fnPtrs
}
}

@available(SwiftStdlib 5.9, *)
extension AttributeInstances: IteratorProtocol {
@available(SwiftStdlib 5.9, *)
@inlinable
public mutating func next() -> T? {
while index < fnPtrs.endIndex {
let fnPtr = fnPtrs[index]
index += 1

typealias AttributeFn = @convention(thin) () -> T?

let fn = unsafeBitCast(fnPtr, to: AttributeFn.self)

guard let attribute = fn() else {
continue
}

return attribute
}

return nil
}
}

@available(SwiftStdlib 5.9, *)
extension AttributeInstances: Sequence {}
1 change: 1 addition & 0 deletions stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt
Expand Up @@ -16,6 +16,7 @@ list(APPEND SWIFT_REFLECTION_SWIFT_FLAGS
"-parse-stdlib")

add_swift_target_library(swiftReflection ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
Attribute.swift
Case.swift
Field.swift
GenericArguments.swift
Expand Down
2 changes: 2 additions & 0 deletions stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt
Expand Up @@ -68,6 +68,8 @@ add_swift_target_library(swift_Runtime ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_ST
ExistentialContainer.swift
Functions.swift
HeapObject.swift
ImageInspection.cpp
ImageInspection.swift
WitnessTable.swift

C_COMPILE_FLAGS
Expand Down
86 changes: 86 additions & 0 deletions stdlib/public/Reflection/Sources/_Runtime/ImageInspection.cpp
@@ -0,0 +1,86 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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
//
//===----------------------------------------------------------------------===//

#include <stdint.h>

#include "swift/Runtime/Config.h"
#include "swift/Threading/Once.h"

extern "C"
void registerAttributes(const void *section, intptr_t size);

//===----------------------------------------------------------------------===//
// Mach-O Image Inspection
//===----------------------------------------------------------------------===//

#if defined(__MACH__)

#include <mach-o/dyld.h>
#include <mach-o/getsect.h>

#if __POINTER_WIDTH__ == 64
typedef mach_header_64 mach_header_platform;
#else
typedef mach_header mach_header_platform;
#endif

void lookupSection(const struct mach_header *header, const char *segment,
const char *section,
void (*registerFunc)(const void *, intptr_t)) {
unsigned long size = 0;

auto sectionData = getsectiondata(
reinterpret_cast<const mach_header_platform *>(header), segment, section,
&size);

registerFunc(sectionData, size);
}

void imageFunc(const struct mach_header *header, intptr_t size) {
lookupSection(header, "__TEXT", "__swift5_rattrs", registerAttributes);
}

extern "C" SWIFT_CC(swift)
void snapshot() {
static swift::once_t token;
swift::once(token, []{
_dyld_register_func_for_add_image(imageFunc);
});
}

#else

#include "swift/shims/MetadataSections.h"

extern "C"
void swift_enumerateAllMetadataSections(
bool (* body)(const swift::MetadataSections *sections, void *context),
void *context
);

bool imageFunc(const swift::MetadataSections *sections, void *context) {
if (sections->version >= 3) {
return false;
}

auto runtimeAttrs = sections->swift5_runtime_attributes;
registerAttributes(reinterpret_cast<const void *>(runtimeAttrs.start),
runtimeAttrs.length);

return false;
}

extern "C" SWIFT_CC(swift)
void snapshot() {
swift_enumerateAllMetadataSections(imageFunc, nullptr);
}

#endif
97 changes: 97 additions & 0 deletions stdlib/public/Reflection/Sources/_Runtime/ImageInspection.swift
@@ -0,0 +1,97 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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
//
//===----------------------------------------------------------------------===//

import Swift

@available(SwiftStdlib 5.9, *)
@frozen
public enum ImageInspection {}

@_silgen_name("snapshot")
func snapshot()

//===----------------------------------------------------------------------===//
// Runtime Attributes
//===----------------------------------------------------------------------===//

@available(SwiftStdlib 5.9, *)
var attributeCache: Lock<[ContextDescriptor: [UnsafeRawPointer]]> = .create(
with: [:]
)

@available(SwiftStdlib 5.9, *)
extension ImageInspection {
@available(SwiftStdlib 5.9, *)
public static func withAttributeCache<T>(
_ body: @Sendable ([ContextDescriptor: [UnsafeRawPointer]]) throws -> T
) rethrows -> T {
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
attributeCache.withLock {
$0.removeAll(keepingCapacity: true)
}
#endif

snapshot()

return try attributeCache.withLock {
try body($0)
}
}
}

@available(SwiftStdlib 5.9, *)
@_cdecl("registerAttributes")
func registerAttributes(section: UnsafeRawPointer, size: Int) {
var address = section
let end = address + size

while address < end {
// Flags (always 0 for right now)
address += MemoryLayout<Int32>.size

let attributeAddr = address.relativeIndirectableAddress(
as: ContextDescriptor.self
)

let attributeDescriptor = ContextDescriptor(attributeAddr)

// Attribute Context Descriptor
address += MemoryLayout<Int32>.size

let numberOfInstances = address.loadUnaligned(as: UInt32.self)

// Number of records this attribute has
address += MemoryLayout<Int32>.size

var fnPtrs: [UnsafeRawPointer] = []
fnPtrs.reserveCapacity(Int(numberOfInstances))

for _ in 0 ..< numberOfInstances {
// The type this attribute was on (not always an actual type)
address += MemoryLayout<Int32>.size

var fnRecord = address.relativeDirectAddress(as: UnsafeRawPointer.self)
fnRecord += MemoryLayout<RelativeDirectPointer<Void>>.size * 3

let fnPtr = fnRecord.relativeDirectAddress(as: UnsafeRawPointer.self)
fnPtrs.append(fnPtr)

// Function pointer to attribute initializer
address += MemoryLayout<Int32>.size
}

let copyFnPtrs = fnPtrs

attributeCache.withLock {
$0[attributeDescriptor, default: []].append(contentsOf: copyFnPtrs)
}
}
}

0 comments on commit 886e68b

Please sign in to comment.