diff --git a/stdlib/public/Reflection/Sources/Reflection/Attribute.swift b/stdlib/public/Reflection/Sources/Reflection/Attribute.swift new file mode 100644 index 0000000000000..c9edcce5567c9 --- /dev/null +++ b/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(attachedTo: KeyPath, _ 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(of type: T.Type) -> AttributeInstances { + 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 { + @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 {} diff --git a/stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt b/stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt index d2c5f6c86f249..2bf11ad907971 100644 --- a/stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt +++ b/stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt @@ -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 diff --git a/stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt b/stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt index 8d5245e728b2d..b706819ba0cc2 100644 --- a/stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt +++ b/stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt @@ -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 diff --git a/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.cpp b/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.cpp new file mode 100644 index 0000000000000..0c25ac387a7b1 --- /dev/null +++ b/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.cpp @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#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 +#include + +#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(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 initializeDyldLookup() { + static swift::once_t token; + swift::once(token, []{ + _dyld_register_func_for_add_image(imageFunc); + }); +} + +#endif diff --git a/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.swift b/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.swift new file mode 100644 index 0000000000000..cdf3c33511a74 --- /dev/null +++ b/stdlib/public/Reflection/Sources/_Runtime/ImageInspection.swift @@ -0,0 +1,175 @@ +//===----------------------------------------------------------------------===// +// +// 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 _SwiftRuntimeShims + +#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) +import SwiftShims +#endif + +@available(SwiftStdlib 5.9, *) +@frozen +public enum ImageInspection {} + +//===----------------------------------------------------------------------===// +// Mach-O Image Lookup +//===----------------------------------------------------------------------===// + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +@_silgen_name("initializeDyldLookup") +func initializeDyldLookup() +#endif + +//===----------------------------------------------------------------------===// +// ELF and COFF Image Lookup +//===----------------------------------------------------------------------===// + +#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) +func enumerateSections( + _ body: (MetadataSections) -> ( + MetadataSectionRange, + (UnsafeRawPointer, Int) -> () + ) +) { + swift_enumerateAllMetadataSections({ sections, _ in + guard let sections = sections else { + return true + } + + let metadataSections = sections.assumingMemoryBound( + to: MetadataSections.self + ) + + guard metadataSections.version >= 3 else { + return false + } + + let (range, register) = body(metadataSections) + + register(range.start, range.length) + + return true + }, nil) +} +#endif + +//===----------------------------------------------------------------------===// +// Runtime Attributes +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 5.9, *) +struct AttributeCache { +#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) + var imageCount = 0 +#endif + var map: [ContextDescriptor: [UnsafeRawPointer]] = [:] +} + +@available(SwiftStdlib 5.9, *) +var attributeCache: Lock = .create(with: .init()) + +@available(SwiftStdlib 5.9, *) +extension ImageInspection { + @available(SwiftStdlib 5.9, *) + public static func withAttributeCache( + _ body: @Sendable ([ContextDescriptor: [UnsafeRawPointer]]) throws -> T + ) rethrows -> T { +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) + initializeDyldLookup() + + return try attributeCache.withLock { + try body($0.map) + } +#else + return try attributeCache.withLock { + // If our cached image count is less than what's being reported by the + // Swift runtime, then we need to reset our cache and enumerate all of + // the images again for updated attributes. + let currentImageCount = swift_getMetadataSectionCount() + + if $0.imageCount < currentImageCount { + $0.map.removeAll(keepingCapacity: true) + + // We enumerate inside the lock because we need to ensure that the + // thread who gets this lock first is able to at least initialize the + // cache before other threads. + enumerateSections { + ($0.swift5_runtime_attributes, registerAttributes(_:_:)) + } + + $0.imageCount = currentImageCount + } + + return try body($0.map) + } +#endif + } +} + +@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.size + + let attributeAddr = address.relativeIndirectableAddress( + as: ContextDescriptor.self + ) + + let attributeDescriptor = ContextDescriptor(attributeAddr) + + // Attribute Context Descriptor + address += MemoryLayout.size + + let numberOfInstances = address.loadUnaligned(as: UInt32.self) + + // Number of records this attribute has + address += MemoryLayout.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.size + + var fnRecord = address.relativeDirectAddress(as: UnsafeRawPointer.self) + fnRecord += MemoryLayout>.size * 3 + + let fnPtr = fnRecord.relativeDirectAddress(as: UnsafeRawPointer.self) + fnPtrs.append(fnPtr) + + // Function pointer to attribute initializer + address += MemoryLayout.size + } + + let copyFnPtrs = fnPtrs + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) + attributeCache.withLock { + $0.map[attributeDescriptor, default: []].append(contentsOf: copyFnPtrs) + } +#else + // For non-Darwin platforms, we have to iterate the images and register + // the attributes inside of the attribute cache. So, we're already in the + // lock by the time this function gets called, so it is safe to access the + // value in our lock without locking. + attributeCache.withUnsafeValue { + $0.map[attributeDescriptor, default: []].append(contentsOf: copyFnPtrs) + } +#endif + } +} diff --git a/stdlib/public/Reflection/Sources/_Runtime/Metadata/ValueWitnessTable.swift b/stdlib/public/Reflection/Sources/_Runtime/Metadata/ValueWitnessTable.swift index 7f41e4f7112ba..239bc3232f9f5 100644 --- a/stdlib/public/Reflection/Sources/_Runtime/Metadata/ValueWitnessTable.swift +++ b/stdlib/public/Reflection/Sources/_Runtime/Metadata/ValueWitnessTable.swift @@ -128,9 +128,9 @@ extension ValueWitnessTable { _ dest: UnsafeMutableRawPointer, _ src: UnsafeRawPointer ) -> UnsafeMutableRawPointer { - let address = layout.raw - - return address.signedVWTInitializeBufferWithCopyOfBuffer( + return address( + for: \.initializeBufferWithCopyOfBuffer + ).signedVWTInitializeBufferWithCopyOfBuffer( dest, src, trailing @@ -139,17 +139,7 @@ extension ValueWitnessTable { @inlinable public func destroy(_ src: UnsafeMutableRawPointer) { - // rdar://103834325 - // FIXME: There's currently a compiler bug preventing me from doing: - // 'address(of: \.destroy)' - // or even - // 'MemoryLayout.offset(of: \.destroy)' - // - // The same goes for everything else in this file - let address = layout.raw - + MemoryLayout.size - - address.signedVWTDestroy(src, trailing) + address(for: \.destroy).signedVWTDestroy(src, trailing) } @inlinable @@ -158,11 +148,9 @@ extension ValueWitnessTable { _ dest: UnsafeMutableRawPointer, _ src: UnsafeRawPointer ) -> UnsafeMutableRawPointer { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTInitializeWithCopy(dest, src, trailing) + return address( + for: \.initializeWithCopy + ).signedVWTInitializeWithCopy(dest, src, trailing) } @inlinable @@ -171,12 +159,9 @@ extension ValueWitnessTable { _ dest: UnsafeMutableRawPointer, _ src: UnsafeRawPointer ) -> UnsafeMutableRawPointer { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTAssignWithCopy(dest, src, trailing) + return address( + for: \.assignWithCopy + ).signedVWTAssignWithCopy(dest, src, trailing) } @inlinable @@ -185,13 +170,9 @@ extension ValueWitnessTable { _ dest: UnsafeMutableRawPointer, _ src: UnsafeMutableRawPointer ) -> UnsafeMutableRawPointer { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTInitializeWithTake(dest, src, trailing) + return address( + for: \.initializeWithTake + ).signedVWTInitializeWithTake(dest, src, trailing) } @inlinable @@ -200,14 +181,9 @@ extension ValueWitnessTable { _ dest: UnsafeMutableRawPointer, _ src: UnsafeMutableRawPointer ) -> UnsafeMutableRawPointer { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTAssignWithTake(dest, src, trailing) + return address( + for: \.assignWithTake + ).signedVWTAssignWithTake(dest, src, trailing) } @inlinable @@ -216,15 +192,9 @@ extension ValueWitnessTable { _ src: UnsafeRawPointer, _ numberOfEmptyCases: UInt32 ) -> UInt32 { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTGetEnumTagSinglePayload( + return address( + for: \.getEnumTagSinglePayload + ).signedVWTGetEnumTagSinglePayload( src, numberOfEmptyCases, trailing @@ -237,16 +207,9 @@ extension ValueWitnessTable { _ tag: UInt32, _ numberOfEmptyCases: UInt32 ) -> () { - let address = layout.raw - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - + MemoryLayout.size - - return address.signedVWTStoreEnumTagSinglePayload( + return address( + for: \.storeEnumTagSinglePayload + ).signedVWTStoreEnumTagSinglePayload( src, tag, numberOfEmptyCases, diff --git a/stdlib/public/Reflection/Sources/_Runtime/Utils/Lock.swift b/stdlib/public/Reflection/Sources/_Runtime/Utils/Lock.swift index 3094995133698..4147fda4c555c 100644 --- a/stdlib/public/Reflection/Sources/_Runtime/Utils/Lock.swift +++ b/stdlib/public/Reflection/Sources/_Runtime/Utils/Lock.swift @@ -43,6 +43,8 @@ class Lock { UnsafeRawPointer.self ) + lock.value = initialValue + _lockInit(lock.mutex) return lock @@ -57,6 +59,11 @@ class Lock { return try body(&value) } + + // Only use this when you know you're already inside the lock. + func withUnsafeValue(_ body: (inout T) throws -> U) rethrows -> U { + try body(&value) + } } extension Lock: @unchecked Sendable {} diff --git a/stdlib/public/Reflection/Sources/_Runtime/Utils/RelativeIndirectablePointer.swift b/stdlib/public/Reflection/Sources/_Runtime/Utils/RelativeIndirectablePointer.swift index 56b61f1a7ac26..9c646b939dc55 100644 --- a/stdlib/public/Reflection/Sources/_Runtime/Utils/RelativeIndirectablePointer.swift +++ b/stdlib/public/Reflection/Sources/_Runtime/Utils/RelativeIndirectablePointer.swift @@ -32,3 +32,17 @@ public struct RelativeIndirectablePointer: RelativePointer { } } } + +extension UnsafeRawPointer { + @available(SwiftStdlib 5.9, *) + @inlinable + public func relativeIndirectableAddress( + as type: T.Type + ) -> UnsafeRawPointer { + let relativePointer = RelativeIndirectablePointer( + offset: loadUnaligned(as: Int32.self) + ) + + return relativePointer.address(from: self) + } +} diff --git a/stdlib/public/SwiftShims/swift/shims/_SwiftRuntime.h b/stdlib/public/SwiftShims/swift/shims/_SwiftRuntime.h index 23645cd6616c2..55f3e39cf0408 100644 --- a/stdlib/public/SwiftShims/swift/shims/_SwiftRuntime.h +++ b/stdlib/public/SwiftShims/swift/shims/_SwiftRuntime.h @@ -19,4 +19,22 @@ extern void *swift_projectBox(void *object); // ProtocolDescriptor *protocol); extern void *swift_conformsToProtocol(const void *type, const void *protocol); +#if !defined(__MACH__) + +#include + +// size_t swift_getMetadataSectionCount(); +extern size_t swift_getMetadataSectionCount(); + +// void swift_enumerateAllMetadataSections( +// bool (*body)(const swift::MetadataSections *sections, void *context), +// void *context +// ) +extern void swift_enumerateAllMetadataSections( + _Bool (*body)(const void *sections, void *context), + void *context +); + +#endif // !defined(__MACH__) + #endif /* SWIFT_REFLECTION_RUNTIME_H */ diff --git a/stdlib/public/runtime/ImageInspectionCommon.cpp b/stdlib/public/runtime/ImageInspectionCommon.cpp index 6461941f95d88..80e652d02dd34 100644 --- a/stdlib/public/runtime/ImageInspectionCommon.cpp +++ b/stdlib/public/runtime/ImageInspectionCommon.cpp @@ -168,6 +168,12 @@ void swift::initializeDynamicReplacementLookup() { void swift::initializeAccessibleFunctionsLookup() { } +SWIFT_RUNTIME_EXPORT +size_t swift_getMetadataSectionCount() { + auto snapshot = swift::registered->snapshot(); + return snapshot.count(); +} + #ifndef NDEBUG SWIFT_RUNTIME_EXPORT @@ -214,12 +220,6 @@ void swift_getMetadataSectionBaseAddress(const swift::MetadataSections *section, *out_expected = section->baseAddress.load(std::memory_order_relaxed); } -SWIFT_RUNTIME_EXPORT -size_t swift_getMetadataSectionCount() { - auto snapshot = swift::registered->snapshot(); - return snapshot.count(); -} - #endif // NDEBUG #endif // !defined(__MACH__) diff --git a/test/stdlib/Reflection/Attributes.swift b/test/stdlib/Reflection/Attributes.swift new file mode 100644 index 0000000000000..92a8e4f4e3fdc --- /dev/null +++ b/test/stdlib/Reflection/Attributes.swift @@ -0,0 +1,47 @@ +// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-feature -Xfrontend RuntimeDiscoverableAttrs) +// REQUIRES: executable_test +// REQUIRES: reflection + +import Reflection + +import StdlibUnittest + +let suite = TestSuite("Attributes") + +@runtimeMetadata +struct Greeting { + let description: String + + init(attachedTo: KeyPath, _ description: String) { + self.description = description + } +} + +extension Greeting: Equatable {} + +struct Struct { + @Greeting("Hello!") + let property: Int +} + +extension Struct: Equatable {} + +if #available(SwiftStdlib 5.9, *) { + suite.test("basic") { + let empty = Attribute.allInstances(of: Struct.self) + expectEqualSequence(empty, []) + + let instances = Attribute.allInstances(of: Greeting.self) + + expectEqualSequence(instances, [ + Greeting(attachedTo: \Struct.property, "Hello!") + ]) + + let instances2 = Attribute.allInstances(of: Greeting.self) + expectEqualSequence(instances2, [ + Greeting(attachedTo: \Struct.property, "Hello!") + ]) + } +} + +runAllTests()