Skip to content

Commit

Permalink
Add a field reflection function that constructs keypaths. (#34815)
Browse files Browse the repository at this point in the history
While the existing _forEachField in ReflectionMirror.swift
already gives the offsets and types for each field, this isn't
enough information to construct a keypath for that field in
order to modify it.

For reference, this should be sufficent to implement the features
described here: (https://forums.swift.org/t/storedpropertyiterable/19218/62)
purely at runtime without any derived conformances for many types.

Note: Since there isn't enough reflection information for
`.mutatingGetSet` fields, this means that we're not able to support
reflecting certain types of fields (functions, nonfinal class fields,
etc). Whether this is an error or not is controlled by the `.ignoreUnknown`
option.
  • Loading branch information
pschuh committed Nov 30, 2020
1 parent 89fab1b commit 4e39e59
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 48 deletions.
4 changes: 4 additions & 0 deletions include/swift/Reflection/Records.h
Expand Up @@ -92,6 +92,10 @@ class FieldRecord {
bool isIndirectCase() const {
return Flags.isIndirectCase();
}

bool isVar() const {
return Flags.isVar();
}
};

struct FieldRecordIterator {
Expand Down
1 change: 1 addition & 0 deletions stdlib/public/SwiftShims/CMakeLists.txt
Expand Up @@ -10,6 +10,7 @@ set(sources
MetadataSections.h
Random.h
RefCount.h
Reflection.h
RuntimeShims.h
RuntimeStubs.h
SwiftStdbool.h
Expand Down
36 changes: 36 additions & 0 deletions stdlib/public/SwiftShims/Reflection.h
@@ -0,0 +1,36 @@
//===--- Reflection.h - Types for access to reflection metadata. ----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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_STDLIB_SHIMS_REFLECTION_H
#define SWIFT_STDLIB_SHIMS_REFLECTION_H

#include "SwiftStdbool.h"
#include "SwiftStdint.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*NameFreeFunc)(const char*);

typedef struct _FieldReflectionMetadata {
const char* name;
NameFreeFunc freeFunc;
__swift_bool isStrong;
__swift_bool isVar;
} _FieldReflectionMetadata;

#ifdef __cplusplus
} // extern "C"
#endif

#endif // SWIFT_STDLIB_SHIMS_REFLECTION_H
1 change: 1 addition & 0 deletions stdlib/public/SwiftShims/module.modulemap
Expand Up @@ -9,6 +9,7 @@ module SwiftShims {
header "MetadataSections.h"
header "Random.h"
header "RefCount.h"
header "Reflection.h"
header "RuntimeShims.h"
header "RuntimeStubs.h"
header "SwiftStdbool.h"
Expand Down
78 changes: 44 additions & 34 deletions stdlib/public/core/KeyPath.swift
Expand Up @@ -1739,6 +1739,40 @@ internal struct KeyPathBuffer {
return UnsafeMutableRawBufferPointer(mutating: data)
}

internal struct Builder {
internal var buffer: UnsafeMutableRawBufferPointer
internal init(_ buffer: UnsafeMutableRawBufferPointer) {
self.buffer = buffer
}
internal mutating func pushRaw(size: Int, alignment: Int)
-> UnsafeMutableRawBufferPointer {
var baseAddress = buffer.baseAddress.unsafelyUnwrapped
var misalign = Int(bitPattern: baseAddress) % alignment
if misalign != 0 {
misalign = alignment - misalign
baseAddress = baseAddress.advanced(by: misalign)
}
let result = UnsafeMutableRawBufferPointer(
start: baseAddress,
count: size)
buffer = UnsafeMutableRawBufferPointer(
start: baseAddress + size,
count: buffer.count - size - misalign)
return result
}
internal mutating func push<T>(_ value: T) {
let buf = pushRaw(size: MemoryLayout<T>.size,
alignment: MemoryLayout<T>.alignment)
buf.storeBytes(of: value, as: T.self)
}
internal mutating func pushHeader(_ header: Header) {
push(header)
// Start the components at pointer alignment
_ = pushRaw(size: RawKeyPathComponent.Header.pointerAlignmentSkew,
alignment: 4)
}
}

internal struct Header {
internal var _value: UInt32

Expand Down Expand Up @@ -2286,40 +2320,16 @@ internal func _appendingKeyPaths<
count: resultSize)
}

func pushRaw(size: Int, alignment: Int)
-> UnsafeMutableRawBufferPointer {
var baseAddress = destBuffer.baseAddress.unsafelyUnwrapped
var misalign = Int(bitPattern: baseAddress) % alignment
if misalign != 0 {
misalign = alignment - misalign
baseAddress = baseAddress.advanced(by: misalign)
}
let result = UnsafeMutableRawBufferPointer(
start: baseAddress,
count: size)
destBuffer = UnsafeMutableRawBufferPointer(
start: baseAddress + size,
count: destBuffer.count - size - misalign)
return result
}
func push<T>(_ value: T) {
let buf = pushRaw(size: MemoryLayout<T>.size,
alignment: MemoryLayout<T>.alignment)
buf.storeBytes(of: value, as: T.self)
}
var destBuilder = KeyPathBuffer.Builder(destBuffer)

// Save space for the header.
let leafIsReferenceWritable = type(of: leaf).kind == .reference
let header = KeyPathBuffer.Header(
destBuilder.pushHeader(KeyPathBuffer.Header(
size: resultSize - MemoryLayout<Int>.size,
trivial: rootBuffer.trivial && leafBuffer.trivial,
hasReferencePrefix: rootBuffer.hasReferencePrefix
|| leafIsReferenceWritable
)
push(header)
// Start the components at pointer alignment
_ = pushRaw(size: RawKeyPathComponent.Header.pointerAlignmentSkew,
alignment: 4)
))

let leafHasReferencePrefix = leafBuffer.hasReferencePrefix

Expand All @@ -2340,13 +2350,13 @@ internal func _appendingKeyPaths<
}

component.clone(
into: &destBuffer,
into: &destBuilder.buffer,
endOfReferencePrefix: endOfReferencePrefix)
// Insert our endpoint type between the root and leaf components.
if let type = type {
push(type)
destBuilder.push(type)
} else {
// Insert our endpoint type between the root and leaf components.
push(Value.self as Any.Type)
destBuilder.push(Value.self as Any.Type)
break
}
}
Expand All @@ -2356,17 +2366,17 @@ internal func _appendingKeyPaths<
let (component, type) = leafBuffer.next()

component.clone(
into: &destBuffer,
into: &destBuilder.buffer,
endOfReferencePrefix: component.header.endOfReferencePrefix)

if let type = type {
push(type)
destBuilder.push(type)
} else {
break
}
}

_internalInvariant(destBuffer.isEmpty,
_internalInvariant(destBuilder.buffer.isEmpty,
"did not fill entire result buffer")
}

Expand Down
94 changes: 86 additions & 8 deletions stdlib/public/core/ReflectionMirror.swift
Expand Up @@ -10,6 +10,8 @@
//
//===----------------------------------------------------------------------===//

import SwiftShims

@_silgen_name("swift_isClassType")
internal func _isClassType(_: Any.Type) -> Bool

Expand All @@ -29,8 +31,7 @@ internal func _getRecursiveChildCount(_: Any.Type) -> Int
internal func _getChildMetadata(
_: Any.Type,
index: Int,
outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
fieldMetadata: UnsafeMutablePointer<_FieldReflectionMetadata>
) -> Any.Type

@_silgen_name("swift_reflectionMirror_recursiveChildOffset")
Expand Down Expand Up @@ -281,14 +282,91 @@ public func _forEachField(
for i in 0..<childCount {
let offset = _getChildOffset(type, index: i)

var nameC: UnsafePointer<CChar>? = nil
var freeFunc: NameFreeFunc? = nil
let childType = _getChildMetadata(
type, index: i, outName: &nameC, outFreeFunc: &freeFunc)
defer { freeFunc?(nameC) }
var field = _FieldReflectionMetadata()
let childType = _getChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
let kind = _MetadataKind(childType)

if !body(field.name!, offset, childType, kind) {
return false
}
}

return true
}

/// Calls the given closure on every field of the specified type.
///
/// If `body` returns `false` for any field, no additional fields are visited.
///
/// - Parameters:
/// - type: The type to inspect.
/// - options: Options to use when reflecting over `type`.
/// - body: A closure to call with information about each field in `type`.
/// The parameters to `body` are a pointer to a C string holding the name
/// of the field, the offset of the field in bytes, the type of the field,
/// and the `_MetadataKind` of the field's type.
/// - Returns: `true` if every invocation of `body` returns `true`; otherwise,
/// `false`.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
@discardableResult
@_spi(Reflection)
public func _forEachFieldWithKeyPath<Root>(
of type: Root.Type,
options: _EachFieldOptions = [],
body: (UnsafePointer<CChar>, PartialKeyPath<Root>) -> Bool
) -> Bool {
// Class types not supported because the metadata does not have
// enough information to construct computed properties.
if _isClassType(type) || options.contains(.classType) {
return false
}
let ignoreUnknown = options.contains(.ignoreUnknown)

let childCount = _getRecursiveChildCount(type)
for i in 0..<childCount {
let offset = _getChildOffset(type, index: i)

var field = _FieldReflectionMetadata()
let childType = _getChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
let kind = _MetadataKind(childType)
let supportedType: Bool
switch kind {
case .struct, .class, .optional, .existential,
.existentialMetatype, .tuple, .enum:
supportedType = true
default:
supportedType = false
}
if !supportedType || !field.isStrong {
if !ignoreUnknown { return false }
continue;
}
func keyPathType<Leaf>(for: Leaf.Type) -> PartialKeyPath<Root>.Type {
if field.isVar { return WritableKeyPath<Root, Leaf>.self }
return KeyPath<Root, Leaf>.self
}
let resultSize = MemoryLayout<Int32>.size + MemoryLayout<Int>.size
let partialKeyPath = _openExistential(childType, do: keyPathType)
._create(capacityInBytes: resultSize) {
var destBuilder = KeyPathBuffer.Builder($0)
destBuilder.pushHeader(KeyPathBuffer.Header(
size: resultSize - MemoryLayout<Int>.size,
trivial: true,
hasReferencePrefix: false
))
let component = RawKeyPathComponent(
header: RawKeyPathComponent.Header(stored: .struct,
mutable: field.isVar,
inlineOffset: UInt32(offset)),
body: UnsafeRawBufferPointer(start: nil, count: 0))
component.clone(
into: &destBuilder.buffer,
endOfReferencePrefix: false)
}

if !body(nameC!, offset, childType, kind) {
if !body(field.name!, partialKeyPath) {
return false
}
}
Expand Down
23 changes: 17 additions & 6 deletions stdlib/public/runtime/ReflectionMirror.cpp
Expand Up @@ -23,6 +23,7 @@
#include "swift/Runtime/Portability.h"
#include "Private.h"
#include "WeakReference.h"
#include "../SwiftShims/Reflection.h"
#include <cassert>
#include <cinttypes>
#include <cstdio>
Expand Down Expand Up @@ -82,6 +83,7 @@ namespace {
class FieldType {
const Metadata *type;
bool indirect;
bool var = false;
TypeReferenceOwnership referenceOwnership;
public:

Expand All @@ -97,6 +99,8 @@ class FieldType {
const TypeReferenceOwnership getReferenceOwnership() const { return referenceOwnership; }
bool isIndirect() const { return indirect; }
void setIndirect(bool value) { indirect = value; }
bool isVar() const { return var; }
void setIsVar(bool value) { var = value; }
void setReferenceOwnership(TypeReferenceOwnership newOwnership) {
referenceOwnership = newOwnership;
}
Expand Down Expand Up @@ -292,7 +296,10 @@ struct TupleImpl : ReflectionMirrorImpl {
// Get the nth element.
auto &elt = Tuple->getElement(i);

return FieldType(elt.Type);
FieldType result(elt.Type);
// All tuples are mutable.
result.setIsVar(true);
return result;
}

AnyReturn subscript(intptr_t i, const char **outName,
Expand Down Expand Up @@ -431,6 +438,7 @@ getFieldAt(const Metadata *base, unsigned index) {
auto fieldType = FieldType(typeInfo.getMetadata());
fieldType.setIndirect(field.isIndirectCase());
fieldType.setReferenceOwnership(typeInfo.getReferenceOwnership());
fieldType.setIsVar(field.isVar());
return {name, fieldType};
}

Expand Down Expand Up @@ -993,17 +1001,20 @@ intptr_t swift_reflectionMirror_recursiveCount(const Metadata *type) {
// func _getChildMetadata(
// type: Any.Type,
// index: Int,
// outName: UnsafeMutablePointer<UnsafePointer<CChar>?>,
// outFreeFunc: UnsafeMutablePointer<NameFreeFunc?>
// fieldMetadata: UnsafeMutablePointer<_FieldReflectionMetadata>
// ) -> Any.Type
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const Metadata *swift_reflectionMirror_recursiveChildMetadata(
const Metadata *type,
intptr_t index,
const char **outName,
void (**outFreeFunc)(const char *)) {
_FieldReflectionMetadata* field) {
return call(nullptr, type, type, [&](ReflectionMirrorImpl *impl) {
return impl->recursiveChildMetadata(index, outName, outFreeFunc).getType();
FieldType fieldInfo = impl->recursiveChildMetadata(index, &field->name,
&field->freeFunc);

field->isStrong = fieldInfo.getReferenceOwnership().isStrong();
field->isVar = fieldInfo.isVar();
return fieldInfo.getType();
});
}

Expand Down

0 comments on commit 4e39e59

Please sign in to comment.