From 886e68ba6e18ee57853ac87bc71f01427d9ed306 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 23 Jan 2023 11:58:50 -0800 Subject: [PATCH] Implement Runtime Attributes in Reflection add docs fix error Fix linux and windows build initialize the lock value --- .../Sources/Reflection/Attribute.swift | 113 ++++++++++++++++++ .../Sources/Reflection/CMakeLists.txt | 1 + .../Sources/_Runtime/CMakeLists.txt | 2 + .../Sources/_Runtime/ImageInspection.cpp | 86 +++++++++++++ .../Sources/_Runtime/ImageInspection.swift | 97 +++++++++++++++ .../_Runtime/Metadata/ValueWitnessTable.swift | 81 ++++--------- .../Sources/_Runtime/Utils/Lock.swift | 2 + .../Utils/RelativeIndirectablePointer.swift | 14 +++ test/stdlib/Reflection/Attributes.swift | 47 ++++++++ 9 files changed, 384 insertions(+), 59 deletions(-) create mode 100644 stdlib/public/Reflection/Sources/Reflection/Attribute.swift create mode 100644 stdlib/public/Reflection/Sources/_Runtime/ImageInspection.cpp create mode 100644 stdlib/public/Reflection/Sources/_Runtime/ImageInspection.swift create mode 100644 test/stdlib/Reflection/Attributes.swift 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..0330109813cea --- /dev/null +++ b/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 + +#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 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(runtimeAttrs.start), + runtimeAttrs.length); + + return false; +} + +extern "C" SWIFT_CC(swift) +void snapshot() { + swift_enumerateAllMetadataSections(imageFunc, nullptr); +} + +#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..b021f3d1a8d3e --- /dev/null +++ b/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( + _ 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.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 + + attributeCache.withLock { + $0[attributeDescriptor, default: []].append(contentsOf: copyFnPtrs) + } + } +} 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..12294705ef8f0 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 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/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()