Skip to content

[SE-0349] Implementation for unaligned loads from raw memory #41033

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 11 commits into from
Apr 18, 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
75 changes: 74 additions & 1 deletion stdlib/public/core/UnsafeRawBufferPointer.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,49 @@ extension Unsafe${Mutable}RawBufferPointer {
return baseAddress!.load(fromByteOffset: offset, as: T.self)
}

/// Returns a new instance of the given type, constructed from the raw memory
/// at the specified offset.
///
/// This function only supports loading trivial types.
/// A trivial type does not contain any reference-counted property
/// within its in-memory stored representation.
/// The memory at `offset` bytes into the buffer must be laid out
/// identically to the in-memory representation of `T`.
///
/// You can use this method to create new values from the buffer pointer's
/// underlying bytes. The following example creates two new `Int32`
/// instances from the memory referenced by the buffer pointer `someBytes`.
/// The bytes for `a` are copied from the first four bytes of `someBytes`,
/// and the bytes for `b` are copied from the next four bytes.
///
/// let a = someBytes.load(as: Int32.self)
/// let b = someBytes.load(fromByteOffset: 4, as: Int32.self)
///
/// The memory to read for the new instance must not extend beyond the buffer
/// pointer's memory region---that is, `offset + MemoryLayout<T>.size` must
/// be less than or equal to the buffer pointer's `count`.
///
/// - Parameters:
/// - offset: The offset, in bytes, into the buffer pointer's memory at
/// which to begin reading data for the new instance. The buffer pointer
/// plus `offset` must be properly aligned for accessing an instance of
/// type `T`. The default is zero.
/// - type: The type to use for the newly constructed instance. The memory
/// must be initialized to a value of a type that is layout compatible
/// with `type`.
/// - Returns: A new instance of type `T`, copied from the buffer pointer's
/// memory.
@_alwaysEmitIntoClient
public func loadUnaligned<T>(
fromByteOffset offset: Int = 0,
as type: T.Type
) -> T {
_debugPrecondition(offset >= 0, "${Self}.load with negative offset")
_debugPrecondition(offset + MemoryLayout<T>.size <= self.count,
"${Self}.load out of bounds")
return baseAddress!.loadUnaligned(fromByteOffset: offset, as: T.self)
}

% if mutable:
/// Stores a value's bytes into the buffer pointer's raw memory at the
/// specified byte offset.
Expand All @@ -399,6 +442,16 @@ extension Unsafe${Mutable}RawBufferPointer {
/// type `U`. Calling `storeBytes(of:toByteOffset:as:)` does not change the
/// bound type of the memory.
///
/// - Note: A trivial type can be copied with just a bit-for-bit copy without
/// any indirection or reference-counting operations. Generally, native
/// Swift types that do not contain strong or weak references or other
/// forms of indirection are trivial, as are imported C structs and enums.
///
/// If you need to store into memory a copy of a value of a type that isn't
/// trivial, you cannot use the `storeBytes(of:toByteOffset:as:)` method.
/// Instead, you must know either initialize the memory or,
/// if you know the memory was already bound to `type`, assign to the memory.
///
/// - Parameters:
/// - value: The value to store as raw bytes.
/// - offset: The offset in bytes into the buffer pointer's memory to begin
Expand All @@ -409,14 +462,34 @@ extension Unsafe${Mutable}RawBufferPointer {
/// must be initialized to a value of a type that is layout compatible
/// with `type`.
@inlinable
@_alwaysEmitIntoClient
@_silgen_name("_swift_se0349_UnsafeMutableRawBufferPointer_storeBytes")
public func storeBytes<T>(
of value: T, toByteOffset offset: Int = 0, as type: T.Type
) {
_debugPrecondition(offset >= 0, "${Self}.storeBytes with negative offset")
_debugPrecondition(offset + MemoryLayout<T>.size <= self.count,
"${Self}.storeBytes out of bounds")

baseAddress!.storeBytes(of: value, toByteOffset: offset, as: T.self)
let pointer = baseAddress._unsafelyUnwrappedUnchecked
pointer.storeBytes(of: value, toByteOffset: offset, as: T.self)
}

// This unavailable implementation uses the expected mangled name
// of `storeBytes<T>(of:toByteOffset:as:)`, and provides an entry point for
// any binary compiled against the stlib binary for Swift 5.6 and older.
@available(*, unavailable)
@_silgen_name("$sSw10storeBytes2of12toByteOffset2asyx_SixmtlF")
@usableFromInline func _legacy_se0349_storeBytes<T>(
of value: T, toByteOffset offset: Int = 0, as type: T.Type
) {
_debugPrecondition(offset >= 0, "${Self}.storeBytes with negative offset")
_debugPrecondition(offset + MemoryLayout<T>.size <= self.count,
"${Self}.storeBytes out of bounds")

baseAddress!._legacy_se0349_storeBytes_internal(
of: value, toByteOffset: offset, as: T.self
)
}

/// Copies the bytes from the given buffer to this buffer's memory.
Expand Down
115 changes: 103 additions & 12 deletions stdlib/public/core/UnsafeRawPointer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,6 @@ public struct UnsafeRawPointer: _Pointer {
let rawPointer = (self + offset)._rawValue

#if compiler(>=5.5) && $BuiltinAssumeAlignment
// TODO: to support misaligned raw loads, simply remove this assumption.
let alignedPointer =
Builtin.assumeAlignment(rawPointer,
MemoryLayout<T>.alignment._builtinWordValue)
Expand All @@ -434,6 +433,36 @@ public struct UnsafeRawPointer: _Pointer {
#endif
}

/// Returns a new instance of the given type, constructed from the raw memory
/// at the specified offset.
///
/// This function only supports loading trivial types,
/// and will trap if this precondition is not met.
/// A trivial type does not contain any reference-counted property
/// within its in-memory representation.
/// The memory at this pointer plus `offset` must be laid out
/// identically to the in-memory representation of `T`.
///
/// - Note: A trivial type can be copied with just a bit-for-bit copy without
/// any indirection or reference-counting operations. Generally, native
/// Swift types that do not contain strong or weak references or other
/// forms of indirection are trivial, as are imported C structs and enums.
///
/// - Parameters:
/// - offset: The offset from this pointer, in bytes. `offset` must be
/// nonnegative. The default is zero.
/// - type: The type of the instance to create.
/// - Returns: A new instance of type `T`, read from the raw bytes at
/// `offset`. The returned instance isn't associated
/// with the value in the range of memory referenced by this pointer.
@_alwaysEmitIntoClient
public func loadUnaligned<T>(
fromByteOffset offset: Int = 0,
as type: T.Type
) -> T {
_debugPrecondition(_isPOD(T.self))
return Builtin.loadRaw((self + offset)._rawValue)
}
}

extension UnsafeRawPointer: Strideable {
Expand Down Expand Up @@ -1120,7 +1149,6 @@ public struct UnsafeMutableRawPointer: _Pointer {
let rawPointer = (self + offset)._rawValue

#if compiler(>=5.5) && $BuiltinAssumeAlignment
// TODO: to support misaligned raw loads, simply remove this assumption.
let alignedPointer =
Builtin.assumeAlignment(rawPointer,
MemoryLayout<T>.alignment._builtinWordValue)
Expand All @@ -1130,11 +1158,41 @@ public struct UnsafeMutableRawPointer: _Pointer {
#endif
}

/// Returns a new instance of the given type, constructed from the raw memory
/// at the specified offset.
///
/// This function only supports loading trivial types,
/// and will trap if this precondition is not met.
/// A trivial type does not contain any reference-counted property
/// within its in-memory representation.
/// The memory at this pointer plus `offset` must be laid out
/// identically to the in-memory representation of `T`.
///
/// - Note: A trivial type can be copied with just a bit-for-bit copy without
/// any indirection or reference-counting operations. Generally, native
/// Swift types that do not contain strong or weak references or other
/// forms of indirection are trivial, as are imported C structs and enums.
///
/// - Parameters:
/// - offset: The offset from this pointer, in bytes. `offset` must be
/// nonnegative. The default is zero.
/// - type: The type of the instance to create.
/// - Returns: A new instance of type `T`, read from the raw bytes at
/// `offset`. The returned instance isn't associated
/// with the value in the range of memory referenced by this pointer.
@_alwaysEmitIntoClient
public func loadUnaligned<T>(
fromByteOffset offset: Int = 0,
as type: T.Type
) -> T {
_debugPrecondition(_isPOD(T.self))
return Builtin.loadRaw((self + offset)._rawValue)
}

/// Stores the given value's bytes into raw memory at the specified offset.
///
/// The type `T` to be stored must be a trivial type. The memory at this
/// pointer plus `offset` must be properly aligned for accessing `T`. The
/// memory must also be uninitialized, initialized to `T`, or initialized to
/// The type `T` to be stored must be a trivial type. The memory
/// must also be uninitialized, initialized to `T`, or initialized to
/// another trivial type that is layout compatible with `T`.
///
/// After calling `storeBytes(of:toByteOffset:as:)`, the memory is
Expand All @@ -1148,14 +1206,14 @@ public struct UnsafeMutableRawPointer: _Pointer {
/// Swift types that do not contain strong or weak references or other
/// forms of indirection are trivial, as are imported C structs and enums.
///
/// If you need to store a copy of a nontrivial value into memory, or to
/// store a value into memory that contains a nontrivial value, you cannot
/// use the `storeBytes(of:toByteOffset:as:)` method. Instead, you must know
/// the type of value previously in memory and initialize or assign the
/// memory. For example, to replace a value stored in a raw pointer `p`,
/// If you need to store into memory a copy of a value of a type that isn't
/// trivial, you cannot use the `storeBytes(of:toByteOffset:as:)` method.
/// Instead, you must know either initialize the memory or,
/// if you know the memory was already bound to `type`, assign to the memory.
/// For example, to replace a value stored in a raw pointer `p`,
/// where `U` is the current type and `T` is the new type, use a typed
/// pointer to access and deinitialize the current value before initializing
/// the memory with a new value.
/// the memory with a new value:
///
/// let typedPointer = p.bindMemory(to: U.self, capacity: 1)
/// typedPointer.deinitialize(count: 1)
Expand All @@ -1167,8 +1225,41 @@ public struct UnsafeMutableRawPointer: _Pointer {
/// nonnegative. The default is zero.
/// - type: The type of `value`.
@inlinable
@_alwaysEmitIntoClient
@_silgen_name("_swift_se0349_UnsafeMutableRawPointer_storeBytes")
public func storeBytes<T>(
of value: T, toByteOffset offset: Int = 0, as type: T.Type
) {
_debugPrecondition(_isPOD(T.self))

withUnsafePointer(to: value) { source in
// FIXME: to be replaced by _memcpy when conversions are implemented.
Builtin.int_memcpy_RawPointer_RawPointer_Int64(
(self + offset)._rawValue,
source._rawValue,
UInt64(MemoryLayout<T>.size)._value,
/*volatile:*/ false._value
)
}
}

// This unavailable implementation uses the expected mangled name
// of `storeBytes<T>(of:toByteOffset:as:)`, and provides an entry point for
// any binary compiled against the stlib binary for Swift 5.6 and older.
@available(*, unavailable)
@_silgen_name("sSv10storeBytes2of12toByteOffset2asyx_SixmtlF")
@usableFromInline func _legacy_se0349_storeBytes<T>(
of value: T, toByteOffset offset: Int = 0, as type: T.Type
) {
_legacy_se0349_storeBytes_internal(
of: value, toByteOffset: offset, as: T.self
)
}

// This is the implementation of `storeBytes` from SwiftStdlib 5.6
@_alwaysEmitIntoClient
internal func _legacy_se0349_storeBytes_internal<T>(
of value: T, toByteOffset offset: Int = 0, as type: T.Type
) {
_debugPrecondition(0 == (UInt(bitPattern: self + offset)
& (UInt(MemoryLayout<T>.alignment) - 1)),
Expand All @@ -1183,7 +1274,7 @@ public struct UnsafeMutableRawPointer: _Pointer {
/*volatile:*/ false._value)
}
}

/// Copies the specified number of bytes from the given raw pointer's memory
/// into this pointer's memory.
///
Expand Down
2 changes: 2 additions & 0 deletions test/api-digester/stability-stdlib-abi-without-asserts.test
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@ Func UnsafeBufferPointer.withMemoryRebound(to:_:) has been removed
Func UnsafeMutableBufferPointer.withMemoryRebound(to:_:) has been removed
Func UnsafeMutablePointer.withMemoryRebound(to:capacity:_:) has been removed
Func UnsafePointer.withMemoryRebound(to:capacity:_:) has been removed
Func UnsafeMutableRawBufferPointer.storeBytes(of:toByteOffset:as:) has been removed
Func UnsafeMutableRawPointer.storeBytes(of:toByteOffset:as:) has been removed

// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)
Loading