Skip to content

SE-0333: Expand usability of withMemoryRebound #39529

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 14 commits into from
Feb 5, 2022
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
95 changes: 73 additions & 22 deletions stdlib/public/core/UnsafeBufferPointer.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,18 @@ extension Unsafe${Mutable}BufferPointer {
/// the same memory as an unrelated type without first rebinding the memory
/// is undefined.
///
/// The entire region of memory referenced by this buffer must be initialized.
/// The number of instances of `T` referenced by the rebound buffer may be
/// different than the number of instances of `Element` referenced by the
/// original buffer. The number of instances of `T` will be calculated
/// at runtime.
///
/// Any instance of `T` within the re-bound region may be initialized or
/// uninitialized. Every instance of `Pointee` overlapping with a given
/// instance of `T` should have the same initialization state (i.e.
/// initialized or uninitialized.) Accessing a `T` whose underlying
/// `Pointee` storage is in a mixed initialization state shall be
/// undefined behaviour.
///
/// Because this buffer's memory is no longer bound to its `Element` type
/// while the `body` closure executes, do not access memory using the
/// original buffer from within `body`. Instead, use the `body` closure's
Expand All @@ -666,39 +676,80 @@ extension Unsafe${Mutable}BufferPointer {
/// `Element` type.
///
/// - Note: Only use this method to rebind the buffer's memory to a type
/// with the same size and stride as the currently bound `Element` type.
/// To bind a region of memory to a type that is a different size, convert
/// the buffer to a raw buffer and use the `bindMemory(to:)` method.
/// that is layout compatible with the currently bound `Element` type.
/// The stride of the temporary type (`T`) may be an integer multiple
/// or a whole fraction of `Element`'s stride.
/// To bind a region of memory to a type that does not match these
/// requirements, convert the buffer to a raw buffer and use the
/// `bindMemory(to:)` method.
/// If `T` and `Element` have different alignments, this buffer's
/// `baseAddress` must be aligned with the larger of the two alignments.
///
/// - Parameters:
/// - type: The type to temporarily bind the memory referenced by this
/// buffer. The type `T` must have the same size and be layout compatible
/// buffer. The type `T` must be layout compatible
/// with the pointer's `Element` type.
/// - body: A closure that takes a ${Mutable.lower()} typed buffer to the
/// same memory as this buffer, only bound to type `T`. The buffer argument
/// contains the same number of complete instances of `T` as the original
/// buffer’s `count`. The closure's buffer argument is valid only for the
/// duration of the closure's execution. If `body` has a return value, that
/// value is also used as the return value for the `withMemoryRebound(to:_:)`
/// same memory as this buffer, only bound to type `T`. The buffer
/// parameter contains a number of complete instances of `T` based
/// on the capacity of the original buffer and the stride of `Element`.
/// The closure's buffer argument is valid only for the duration of the
/// closure's execution. If `body` has a return value, that value
/// is also used as the return value for the `withMemoryRebound(to:_:)`
/// method.
/// - buffer: The buffer temporarily bound to `T`.
/// - Returns: The return value, if any, of the `body` closure parameter.
@inlinable // unsafe-performance
@_alwaysEmitIntoClient
// This custom silgen name is chosen to not interfere with the old ABI
% if Mutable:
@_silgen_name("$_swift_se0333_UnsafeMutableBufferPointer_withMemoryRebound")
% else:
@_silgen_name("$_swift_se0333_UnsafeBufferPointer_withMemoryRebound")
% end
public func withMemoryRebound<T, Result>(
to type: T.Type, _ body: (${Self}<T>) throws -> Result
to type: T.Type,
_ body: (_ buffer: ${Self}<T>) throws -> Result
) rethrows -> Result {
if let base = _position {
_debugPrecondition(MemoryLayout<Element>.stride == MemoryLayout<T>.stride)
Builtin.bindMemory(base._rawValue, count._builtinWordValue, T.self)
defer {
Builtin.bindMemory(base._rawValue, count._builtinWordValue, Element.self)
}

return try body(${Self}<T>(
start: Unsafe${Mutable}Pointer<T>(base._rawValue), count: count))
guard let base = _position?._rawValue else {
return try body(.init(start: nil, count: 0))
}
else {
return try body(${Self}<T>(start: nil, count: 0))

let newCount: Int
if MemoryLayout<T>.stride == MemoryLayout<Element>.stride {
newCount = count
_debugPrecondition(
MemoryLayout<T>.alignment == MemoryLayout<Element>.alignment
)
} else {
newCount = count * MemoryLayout<Element>.stride / MemoryLayout<T>.stride
_debugPrecondition(
Int(bitPattern: .init(base)) & (MemoryLayout<T>.alignment-1) == 0 &&
MemoryLayout<T>.stride > MemoryLayout<Element>.stride
? MemoryLayout<T>.stride % MemoryLayout<Element>.stride == 0
: MemoryLayout<Element>.stride % MemoryLayout<T>.stride == 0
)
}
let binding = Builtin.bindMemory(base, newCount._builtinWordValue, T.self)
defer { Builtin.rebindMemory(base, binding) }
return try body(.init(start: .init(base), count: newCount))
}

// This unavailable implementation uses the expected mangled name
// of `withMemoryRebound`, and provides an entry point for any
// binary compiled against the stlib binary for Swift 5.6 and older.
@available(*, unavailable)
% if Mutable:
@_silgen_name("$sSr17withMemoryRebound2to_qd_0_qd__m_qd_0_Sryqd__GKXEtKr0_lF")
% else:
@_silgen_name("$sSR17withMemoryRebound2to_qd_0_qd__m_qd_0_SRyqd__GKXEtKr0_lF")
% end
@usableFromInline
internal func _legacy_se0333_withMemoryRebound<T, Result>(
to type: T.Type,
_ body: (${Self}<T>) throws -> Result
) rethrows -> Result {
return try withMemoryRebound(to: T.self, body)
}

/// A pointer to the first element of the buffer.
Expand Down
148 changes: 110 additions & 38 deletions stdlib/public/core/UnsafePointer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ public struct UnsafePointer<Pointee>: _Pointer {
}
}

/// Executes the given closure while temporarily binding the specified number
/// of instances to the given type.
/// Executes the given closure while temporarily binding memory to
/// the specified number of instances of type `T`.
///
/// Use this method when you have a pointer to memory bound to one type and
/// you need to access that memory as instances of another type. Accessing
Expand All @@ -253,15 +253,23 @@ public struct UnsafePointer<Pointee>: _Pointer {
/// the same memory as an unrelated type without first rebinding the memory
/// is undefined.
///
/// The region of memory starting at this pointer and covering `count`
/// instances of the pointer's `Pointee` type must be initialized.
/// The region of memory that starts at this pointer and covers `count`
/// strides of `T` instances must be bound to `Pointee`.
/// Any instance of `T` within the re-bound region may be initialized or
/// uninitialized. Every instance of `Pointee` overlapping with a given
/// instance of `T` should have the same initialization state (i.e.
/// initialized or uninitialized.) Accessing a `T` whose underlying
/// `Pointee` storage is in a mixed initialization state shall be
/// undefined behaviour.
///
/// The following example temporarily rebinds the memory of a `UInt64`
/// pointer to `Int64`, then accesses a property on the signed integer.
///
/// let uint64Pointer: UnsafePointer<UInt64> = fetchValue()
/// let isNegative = uint64Pointer.withMemoryRebound(to: Int64.self, capacity: 1) { ptr in
/// return ptr.pointee < 0
/// let isNegative = uint64Pointer.withMemoryRebound(
/// to: Int64.self, capacity: 1
/// ) {
/// return $0.pointee < 0
/// }
///
/// Because this pointer's memory is no longer bound to its `Pointee` type
Expand All @@ -274,31 +282,60 @@ public struct UnsafePointer<Pointee>: _Pointer {
/// `Pointee` type.
///
/// - Note: Only use this method to rebind the pointer's memory to a type
/// with the same size and stride as the currently bound `Pointee` type.
/// To bind a region of memory to a type that is a different size, convert
/// the pointer to a raw pointer and use the `bindMemory(to:capacity:)`
/// method.
/// that is layout compatible with the `Pointee` type. The stride of the
/// temporary type (`T`) may be an integer multiple or a whole fraction
/// of `Pointee`'s stride, for example to point to one element of
/// an aggregate.
/// To bind a region of memory to a type that does not match these
/// requirements, convert the pointer to a raw pointer and use the
/// `bindMemory(to:)` method.
/// If `T` and `Pointee` have different alignments, this pointer
/// must be aligned with the larger of the two alignments.
///
/// - Parameters:
/// - type: The type to temporarily bind the memory referenced by this
/// pointer. The type `T` must be the same size and be layout compatible
/// pointer. The type `T` must be layout compatible
/// with the pointer's `Pointee` type.
/// - count: The number of instances of `Pointee` to bind to `type`.
/// - body: A closure that takes a typed pointer to the
/// - count: The number of instances of `T` in the re-bound region.
/// - body: A closure that takes a typed pointer to the
/// same memory as this pointer, only bound to type `T`. The closure's
/// pointer argument is valid only for the duration of the closure's
/// execution. If `body` has a return value, that value is also used as
/// the return value for the `withMemoryRebound(to:capacity:_:)` method.
/// - pointer: The pointer temporarily bound to `T`.
/// - Returns: The return value, if any, of the `body` closure parameter.
@inlinable
public func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int,
@_alwaysEmitIntoClient
// This custom silgen name is chosen to not interfere with the old ABI
@_silgen_name("_swift_se0333_UnsafePointer_withMemoryRebound")
public func withMemoryRebound<T, Result>(
to type: T.Type,
capacity count: Int,
_ body: (_ pointer: UnsafePointer<T>) throws -> Result
) rethrows -> Result {
_debugPrecondition(
Int(bitPattern: .init(_rawValue)) & (MemoryLayout<T>.alignment-1) == 0 &&
MemoryLayout<Pointee>.stride > MemoryLayout<T>.stride
? MemoryLayout<Pointee>.stride % MemoryLayout<T>.stride == 0
: MemoryLayout<T>.stride % MemoryLayout<Pointee>.stride == 0
)
let binding = Builtin.bindMemory(_rawValue, count._builtinWordValue, T.self)
defer { Builtin.rebindMemory(_rawValue, binding) }
return try body(.init(_rawValue))
}

// This unavailable implementation uses the expected mangled name
// of `withMemoryRebound`, and provides an entry point for any
// binary compiled against the stlib binary for Swift 5.6 and older.
@available(*, unavailable)
@_silgen_name("$sSP17withMemoryRebound2to8capacity_qd_0_qd__m_Siqd_0_SPyqd__GKXEtKr0_lF")
@usableFromInline
internal func _legacy_se0333_withMemoryRebound<T, Result>(
to type: T.Type,
capacity count: Int,
_ body: (UnsafePointer<T>) throws -> Result
) rethrows -> Result {
Builtin.bindMemory(_rawValue, count._builtinWordValue, T.self)
defer {
Builtin.bindMemory(_rawValue, count._builtinWordValue, Pointee.self)
}
return try body(UnsafePointer<T>(_rawValue))
return try withMemoryRebound(to: T.self, capacity: count, body)
}

/// Accesses the pointee at the specified offset from this pointer.
Expand Down Expand Up @@ -894,8 +931,8 @@ public struct UnsafeMutablePointer<Pointee>: _Pointer {
return UnsafeMutableRawPointer(self)
}

/// Executes the given closure while temporarily binding the specified number
/// of instances to the given type.
/// Executes the given closure while temporarily binding memory to
/// the specified number of instances of the given type.
///
/// Use this method when you have a pointer to memory bound to one type and
/// you need to access that memory as instances of another type. Accessing
Expand All @@ -904,15 +941,21 @@ public struct UnsafeMutablePointer<Pointee>: _Pointer {
/// the same memory as an unrelated type without first rebinding the memory
/// is undefined.
///
/// The region of memory starting at this pointer and covering `count`
/// instances of the pointer's `Pointee` type must be initialized.
/// The region of memory that starts at this pointer and covers `count`
/// strides of `T` instances must be bound to `Pointee`.
/// Any instance of `T` within the re-bound region may be initialized or
/// uninitialized. Every instance of `Pointee` overlapping with a given
/// instance of `T` should have the same initialization state (i.e.
/// initialized or uninitialized.) Accessing a `T` whose underlying
/// `Pointee` storage is in a mixed initialization state shall be
/// undefined behaviour.
///
/// The following example temporarily rebinds the memory of a `UInt64`
/// pointer to `Int64`, then accesses a property on the signed integer.
/// pointer to `Int64`, then modifies the signed integer.
///
/// let uint64Pointer: UnsafeMutablePointer<UInt64> = fetchValue()
/// let isNegative = uint64Pointer.withMemoryRebound(to: Int64.self, capacity: 1) { ptr in
/// return ptr.pointee < 0
/// uint64Pointer.withMemoryRebound(to: Int64.self, capacity: 1) {
/// $0.pointee.negate()
/// }
///
/// Because this pointer's memory is no longer bound to its `Pointee` type
Expand All @@ -925,31 +968,60 @@ public struct UnsafeMutablePointer<Pointee>: _Pointer {
/// `Pointee` type.
///
/// - Note: Only use this method to rebind the pointer's memory to a type
/// with the same size and stride as the currently bound `Pointee` type.
/// To bind a region of memory to a type that is a different size, convert
/// the pointer to a raw pointer and use the `bindMemory(to:capacity:)`
/// method.
/// that is layout compatible with the `Pointee` type. The stride of the
/// temporary type (`T`) may be an integer multiple or a whole fraction
/// of `Pointee`'s stride, for example to point to one element of
/// an aggregate.
/// To bind a region of memory to a type that does not match these
/// requirements, convert the pointer to a raw pointer and use the
/// `bindMemory(to:)` method.
/// If `T` and `Pointee` have different alignments, this pointer
/// must be aligned with the larger of the two alignments.
///
/// - Parameters:
/// - type: The type to temporarily bind the memory referenced by this
/// pointer. The type `T` must be the same size and be layout compatible
/// pointer. The type `T` must be layout compatible
/// with the pointer's `Pointee` type.
/// - count: The number of instances of `Pointee` to bind to `type`.
/// - count: The number of instances of `T` in the re-bound region.
/// - body: A closure that takes a mutable typed pointer to the
/// same memory as this pointer, only bound to type `T`. The closure's
/// pointer argument is valid only for the duration of the closure's
/// execution. If `body` has a return value, that value is also used as
/// the return value for the `withMemoryRebound(to:capacity:_:)` method.
/// - pointer: The pointer temporarily bound to `T`.
/// - Returns: The return value, if any, of the `body` closure parameter.
@inlinable
public func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int,
@_alwaysEmitIntoClient
// This custom silgen name is chosen to not interfere with the old ABI
@_silgen_name("$_swift_se0333_UnsafeMutablePointer_withMemoryRebound")
public func withMemoryRebound<T, Result>(
to type: T.Type,
capacity count: Int,
_ body: (_ pointer: UnsafeMutablePointer<T>) throws -> Result
) rethrows -> Result {
_debugPrecondition(
Int(bitPattern: .init(_rawValue)) & (MemoryLayout<T>.alignment-1) == 0 &&
MemoryLayout<Pointee>.stride > MemoryLayout<T>.stride
? MemoryLayout<Pointee>.stride % MemoryLayout<T>.stride == 0
: MemoryLayout<T>.stride % MemoryLayout<Pointee>.stride == 0
)
let binding = Builtin.bindMemory(_rawValue, count._builtinWordValue, T.self)
defer { Builtin.rebindMemory(_rawValue, binding) }
return try body(.init(_rawValue))
}

// This unavailable implementation uses the expected mangled name
// of `withMemoryRebound`, and provides an entry point for any
// binary compiled against the stlib binary for Swift 5.6 and older.
@available(*, unavailable)
@_silgen_name("$sSp17withMemoryRebound2to8capacity_qd_0_qd__m_Siqd_0_Spyqd__GKXEtKr0_lF")
@usableFromInline
internal func _legacy_se0333_withMemoryRebound<T, Result>(
to type: T.Type,
capacity count: Int,
_ body: (UnsafeMutablePointer<T>) throws -> Result
) rethrows -> Result {
Builtin.bindMemory(_rawValue, count._builtinWordValue, T.self)
defer {
Builtin.bindMemory(_rawValue, count._builtinWordValue, Pointee.self)
}
return try body(UnsafeMutablePointer<T>(_rawValue))
return try withMemoryRebound(to: T.self, capacity: count, body)
}

/// Accesses the pointee at the specified offset from this pointer.
Expand Down
Loading